// ==UserScript== // @name 大众云学|继续教育脚本|yxlearning|山东|职称|自动学习 // @namespace 无 // @version 2.3 // @description 智能续播 (强化) + 自动跳题/答题屏蔽 + 静音 + 控制台日志,根据mumu+AI 1.0 使用Gemini改进。适用于大众云学平台yxlearning.com中的公需课和专业课内容.导入油猴脚本使用,浏览器需开启开发者模式,注意事项:在某些情况下需要自己点击进度条跳转进度,刷课尚不能完全自动化。 // @author 根据mumu+AI 1.0 改进版本,by Yang // @match *://*.zyk.yxlearning.com/learning/index?* // @match *://*.gxk.yxlearning.com/learning/index?* // @grant none // @run-at document-start // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; console.log('[yxlearning脚本] V2.3 脚本已启动 (document-start)。'); try { const INTERVAL_NEXT_VIDEO = 2000; const INTERVAL_SKIP_QUESTION = 2000; const INTERVAL_RESUME = 5000; const INTERVAL_CLICK_SKIP = 3000; const INTERVAL_CHECK_PLAYBACK = 2000; const FALLBACK_CHECK_PLAYBACK_DELAY = 5000; const INITIAL_PLAY_ATTEMPT_DELAY = 1000; const INTERVAL_AUTO_MUTE = 1000; // const INTERVAL_REMOVE_ADS = 500; // 这个变量没有实际使用,可以移除 let autoPlayIntervalId = null; /***** 0. 早期 CSS 隐藏 *****/ const domain = location.hostname; const hideSelectors = { 'sddz.gxk.yxlearning.com': [ '.bplayer-question-wrap', '.question-modal-container', '.pv-ask-modal-wrap', '.ad-container', '.popup-wrapper', '.pv-mask' ], 'sddz.zyk.yxlearning.com': [ '.bplayer-question-wrap', '.pv-ask-modal-wrap', '.pv-mask', '.ad-container', // <--- 新增 '.popup-wrapper' // <--- 新增 ] }; function addCssToHideElements() { const selectorsToHide = hideSelectors[domain] || []; if (selectorsToHide.length > 0) { const style = document.createElement('style'); style.type = 'text/css'; // 确保对 body 和 html 元素的溢出处理,防止滚动条问题 style.textContent = ` ${selectorsToHide.map(s => `${s} { display: none !important; visibility: hidden !important; opacity: 0 !important; pointer-events: none !important; }`).join('\n')} /* 尝试解决某些弹窗导致的背景滚动 */ .modal-open { overflow: hidden !important; } body:has(.modal-open) { overflow: hidden !important; } `; document.head.appendChild(style); console.log('[早期隐藏] 注入 CSS 规则以隐藏:', selectorsToHide.join(', ')); } } // 在DOM解析开始时就注入CSS,争取最早时间隐藏 addCssToHideElements(); /** * 模拟自然点击事件 * @param {HTMLElement} el - 要点击的元素 */ function simulateNaturalClick(el) { if (!el) { console.log('[模拟点击] 目标元素不存在,无法模拟点击。'); return; } const rect = el.getBoundingClientRect(); // 确保元素在视口内,否则点击可能无效 if (rect.width === 0 || rect.height === 0 || rect.top < 0 || rect.left < 0 || rect.bottom > window.innerHeight || rect.right > window.innerWidth) { console.log('[模拟点击] 目标元素不在视口内或大小为零,跳过点击。', el); return; } const x = rect.left + rect.width / 2; const y = rect.top + rect.height / 2; ['mousedown', 'mouseup', 'click'].forEach(type => { el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, clientX: x, clientY: y, view: window })); }); console.log('[模拟点击] 成功模拟点击事件。'); } /** * 模拟点击视频播放区域以绕过自动播放限制 */ function simulateClickPlayArea() { const playerArea = document.querySelector('.bplayer-wrap') || document.querySelector('.pv-video-player') || document.querySelector('.player-container'); if (playerArea) { console.log('[自动播放] 尝试模拟点击视频播放区域以绕过自动播放限制。'); simulateNaturalClick(playerArea); } else { console.log('[自动播放] 未找到视频播放区域 (.bplayer-wrap, .pv-video-player 或 .player-container),无法模拟点击。'); } } /***** 1. 自动播放下一视频 *****/ /** * 获取当前正在播放的视频列表项 * @returns {HTMLElement|null} 当前视频的li元素 */ function getCurrentVideoLi() { return document.querySelector('li.videoLi.active') || document.querySelector('.course-list-item.active'); } /** * 获取视频列表项上的进度文本 * @param {HTMLElement} li - 视频列表项元素 * @returns {string} 进度文本,如 "100%" */ function getBadgeText(li) { const badge = li.querySelector('.badge') || li.querySelector('.status-tag'); return badge ? badge.textContent.trim() : ''; } /** * 点击播放下一集视频 */ function clickNextVideo() { const cur = getCurrentVideoLi(); if (!cur) { // console.log('[下一视频] 未找到当前视频项。'); return; } const text = getBadgeText(cur); if (text === '100%' || text === '100') { // 检查当前视频是否已完成 let next = cur.nextElementSibling; // 查找下一个有效的视频列表项 while (next && !(next.classList.contains('videoLi') || next.classList.contains('course-list-item'))) { next = next.nextElementSibling; } if (next) { const trg = next.querySelector('.video-info') || next.querySelector('.course-item-title'); if (trg && trg.offsetParent !== null) { // 检查元素是否可见 simulateNaturalClick(trg); // 使用模拟点击 console.log('[下一视频] 尝试点击下一集。'); } else { console.log('[下一视频] 找到下一集,但其目标元素不可见或不存在,跳过点击。'); } } else { // console.log('[下一视频] 未找到下一集视频。'); } } } /***** 2. 智能续播(自然模拟点击) *****/ let hasUserPlayed = false, retryCount = 0, MAX_RETRY = 5; /** * 检查视频是否处于暂停状态 * @returns {boolean} 如果视频暂停则返回true */ function isPaused() { const videoElement = document.querySelector('video'); if (videoElement) { return videoElement.paused; } // 兼容旧播放器 const wrap = document.querySelector('.bplayer-wrap'); return wrap && !wrap.classList.contains('bplayer-playing'); } /** * 获取视频播放/暂停按钮或区域 * @returns {HTMLElement|null} 播放区域或按钮元素 */ function getPlayArea() { const newPlayButton = document.getElementById('play'); const oldPlayPauseButton = document.querySelector('.pv-playpause.pv-icon-btn-play'); const bplayerPlayPauseButton = document.querySelector('.bplayer-playpause.bplayer-btn-play'); // 优先返回可见的播放按钮 if (newPlayButton && newPlayButton.offsetParent !== null) return newPlayButton; if (oldPlayPauseButton && oldPlayPauseButton.offsetParent !== null) return oldPlayPauseButton; if (bplayerPlayPauseButton && bplayerPlayPauseButton.offsetParent !== null) return bplayerPlayPauseButton; // 否则返回播放器容器 return document.querySelector('.bplayer-control-full') || document.querySelector('.bplayer-wrap') || document.querySelector('.pv-video-player') || document.querySelector('.player-container'); } /** * 监听视频首次播放状态,标记用户已播放 */ function watchFirstPlay() { const videoElement = document.querySelector('video'); if (videoElement) { // 监听 video 元素是否开始播放 videoElement.addEventListener('playing', () => { if (!hasUserPlayed) { hasUserPlayed = true; console.log('[智能续播] 检测到