// ==UserScript== // @name 大众云学继续教育脚本:yxlearning // @version 2.2.1 // @match *://*.zyk.yxlearning.com/learning/index?* // @match *://*.gxk.yxlearning.com/learning/index?* // @run-at document-start // @license MIT // @namespace 无 // @description 下一集自动播放 + 智能续播 (强化) + 自动跳题 + 强制音频 + 继续教育跳题 + 广告/答题屏蔽 + 自动静音 + 控制台日志 // @downloadURL none // ==/UserScript== (function() { 'use strict'; console.log('[yxlearning脚本] V2.2.1 脚本已启动 (document-start)。'); try { const INTERVAL_NEXT_VIDEO = 2000; const INTERVAL_SKIP_QUESTION = 2000; const INTERVAL_RESUME = 3000; // 智能续播检查频率提高 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_ACTIVE_SIMULATION = 5000; // 新增:模拟用户活跃度检测频率 const PLAYBACK_PROGRESS_THRESHOLD = 0.1; // 播放进度至少要前进这么多秒,才算“播放中” const PLAYBACK_CHECK_INTERVAL = 4000; // 播放进度检测的间隔 let autoPlayIntervalId = null; let activeSimulationIntervalId = null; // 新增:用户活跃度模拟定时器ID let lastPlaybackTime = 0; // 上次检测到的播放时间 let playbackCheckTimer = null; // 播放进度检测定时器 /***** 0. 早期 CSS 隐藏 *****/ const domain = location.hostname; // 调整 hideSelectors 匹配所有子域名,而不只是 sddz const hideSelectors = { // 通用规则,应用于所有匹配的子域名 default: [ '.bplayer-question-wrap', '.question-modal-container', '.pv-ask-modal-wrap', '.ad-container', '.popup-wrapper', '.pv-mask', '.layer-dialog' // 新增:通用弹窗类名 ] }; function addCssToHideElements() { // 获取适用的隐藏选择器,如果没有特定域名的,就用 default const selectorsToHide = hideSelectors[domain] || hideSelectors.default || []; if (selectorsToHide.length > 0) { const style = document.createElement('style'); style.type = 'text/css'; 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(', ')); } } addCssToHideElements(); /** * 模拟自然点击事件 * @param {HTMLElement} el - 要点击的元素 */ function simulateNaturalClick(el) { if (!el) { console.log('[模拟点击] 目标元素不存在,无法模拟点击。'); return false; // 返回 false 表示点击未成功 } 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 false; } 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('[模拟点击] 成功模拟点击事件。', el); return true; // 返回 true 表示点击成功 } /** * 模拟鼠标移动,以保持用户活跃度 */ function simulateMouseMove() { // 在视频播放器区域内模拟小范围的鼠标移动 const playerArea = document.querySelector('.bplayer-wrap') || document.querySelector('.pv-video-player') || document.querySelector('.player-container'); if (playerArea) { const rect = playerArea.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) { const x = rect.left + rect.width * Math.random(); const y = rect.top + rect.height * Math.random(); playerArea.dispatchEvent(new MouseEvent('mousemove', { 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) { // 检查元素是否可见 if (simulateNaturalClick(trg)) { // 使用模拟点击,并检查是否成功 console.log('[下一视频] 尝试点击下一集。'); // 点击下一集后,重置智能续播的状态,以便新视频能够被触发播放 hasUserPlayed = false; retryCount = 0; lastPlaybackTime = 0; // 重置播放时间 // 立即尝试点击播放区域,应对新加载视频的自动播放限制 setTimeout(simulateClickPlayArea, 500); } } else { console.log('[下一视频] 找到下一集,但其目标元素不可见或不存在,跳过点击。'); } } else { // console.log('[下一视频] 未找到下一集视频。'); } } } /***** 2. 智能续播(自然模拟点击) *****/ let hasUserPlayed = false; let retryCount = 0; // const MAX_RETRY = 10; // 增加重试次数 - 这一行被移除或注释掉了,实现无限重试 /** * 检查视频是否处于暂停状态 * 新增:根据 currentTime 变化判断是否真正播放 * @returns {boolean} 如果视频暂停或假播放则返回true */ function isPaused() { const videoElement = document.querySelector('video'); const bplayerWrap = document.querySelector('.bplayer-wrap'); // 1. 优先检查 bplayer-wrap 类名 (根据你的反馈,这是最直接的播放状态指示) if (bplayerWrap) { if (bplayerWrap.classList.contains('bplayer-playing')) { // console.log('[播放状态] bplayer-wrap: 正在播放。'); return false; // 正在播放 } else { // console.log('[播放状态] bplayer-wrap: 暂停状态。'); return true; // 暂停状态 } } // 2. 如果没有 bplayer-wrap 或者 bplayer-wrap 不可靠,检查