// ==UserScript== // @name 辽宁干部在线学习 - 精确点击“确定”按钮 + 视频循环播放 // @namespace https://zyjs.lngbzx.gov.cn/ // @version 1.2 // @description 1. 精确点击弹出“确定”按钮 2. 监测视频状态并自动循环播放 // @author You // @match https://zyjs.lngbzx.gov.cn/pc/index.html* // @run-at document-start // @grant none // ==/UserScript== (async function () { 'use strict'; // ========== 1. 基础反检测(屏蔽 webdriver 属性) ========== const unsetWebdriver = () => { Object.defineProperty(navigator, 'webdriver', { get: () => undefined, }); }; unsetWebdriver(); const originalDefineProperty = Object.defineProperty; Object.defineProperty = function (obj, prop, desc) { if (obj === navigator && prop === 'webdriver') { return obj; } return originalDefineProperty.call(this, obj, prop, desc); }; // ========== 2. 辅助函数 ========== const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); const rand = (min, max) => Math.random() * (max - min) + min; // 带随机抖动的线性轨迹生成(如需更逼真可以使用贝塞尔曲线) function generateTrajectory(startX, startY, endX, endY, steps) { const points = []; for (let i = 0; i <= steps; i++) { const t = i / steps; let x = startX + (endX - startX) * t + rand(-1.5, 1.5); let y = startY + (endY - startY) * t + rand(-1.5, 1.5); points.push({ x, y }); } return points; } // 在 document 上触发 mousemove 事件 function dispatchMouseMove(x, y) { const event = new MouseEvent('mousemove', { view: window, bubbles: true, cancelable: true, clientX: x, clientY: y, screenX: x + (window.screenX || 0), screenY: y + (window.screenY || 0), }); document.dispatchEvent(event); } // ========== 3. 真人模拟点击核心 ========== async function humanClick(element) { const box = element.getBoundingClientRect(); if (!box || box.width === 0 || box.height === 0) return false; // 目标坐标(元素内随机) const targetX = box.left + rand(1, box.width - 1); const targetY = box.top + rand(1, box.height - 1); // 起始坐标(视口内随机) const startX = rand(50, window.innerWidth - 50); const startY = rand(50, window.innerHeight - 50); // 移动步数随机 const steps = Math.floor(rand(20, 32)); const trajectory = generateTrajectory(startX, startY, targetX, targetY, steps); // 逐步移动 for (const point of trajectory) { dispatchMouseMove(point.x, point.y); await sleep(rand(5, 16)); } // 最终定位并停顿 dispatchMouseMove(targetX, targetY); await sleep(rand(130, 400)); // 按下 const down = new MouseEvent('mousedown', { view: window, bubbles: true, cancelable: true, clientX: targetX, clientY: targetY, button: 0, }); element.dispatchEvent(down); await sleep(rand(40, 100)); // 松开 const up = new MouseEvent('mouseup', { view: window, bubbles: true, cancelable: true, clientX: targetX, clientY: targetY, button: 0, }); element.dispatchEvent(up); // 补充 click 事件 const click = new MouseEvent('click', { view: window, bubbles: true, cancelable: true, clientX: targetX, clientY: targetY, button: 0, }); element.dispatchEvent(click); console.log(`✅ 已点击按钮,坐标:(${targetX.toFixed(0)}, ${targetY.toFixed(0)})`); return true; } // ========== 4. 使用精确选择器查找“确定”按钮 ========== function findConfirmButton() { // 使用您提供的 CSS 选择器路径 const selector = "#app > div.bodys.is_cont > div:nth-child(5) > div > div.el-dialog__footer > span > button"; const btn = document.querySelector(selector); if (btn && btn.offsetParent !== null && btn.clientHeight > 0 && btn.clientWidth > 0) { return btn; } return null; } // ========== 5. 轮询等待“确定”按钮出现 ========== async function waitForConfirmButton(timeout = 12000) { const start = Date.now(); while (Date.now() - start < timeout) { const btn = findConfirmButton(); if (btn) return btn; await sleep(400); } return null; } // ========== 6. 视频自动循环播放功能 ========== function findPlayButton(video) { // 在视频附近查找可能的播放按钮(兼容 ckplayer 等多种播放器) const parent = video.closest('.video-container, .ckplayer, .player, .video-wrap, .prism-player') || video.parentElement; if (!parent) return null; const selectors = [ '.play-btn', '.play', '.vjs-play-control', '.ckplayer-play', 'button[aria-label="播放"]', 'button[title="播放"]', '[class*="play"]', '.prism-play-btn', '.xh-play-btn' ]; for (const sel of selectors) { const btn = parent.querySelector(sel); if (btn && btn.offsetParent !== null) return btn; } return null; } function attachVideoListeners(video) { if (video.dataset.autoReplay) return; video.dataset.autoReplay = 'true'; console.log('🎬 监测到视频,已绑定自动续播事件'); const replay = async () => { try { await video.play(); console.log('▶️ 视频自动续播成功'); } catch (err) { console.warn('⚠️ video.play() 失败,尝试点击播放按钮…', err); const playBtn = findPlayButton(video); if (playBtn) { await humanClick(playBtn); console.log('🖱️ 已模拟点击播放按钮'); } } }; video.addEventListener('ended', replay); video.addEventListener('pause', () => { // 只在非结束状态下暂停时自动播放(避免重复触发) if (!video.ended) { replay(); } }); } function enableVideoAutoReplay() { console.log('🔄 启动视频循环播放监控…'); // 处理当前已存在的视频 document.querySelectorAll('video').forEach(v => attachVideoListeners(v)); // 监听新增的视频元素 const observer = new MutationObserver(mutations => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType === 1) { if (node.tagName === 'VIDEO') { attachVideoListeners(node); } else if (node.querySelectorAll) { node.querySelectorAll('video').forEach(v => attachVideoListeners(v)); } } } } }); observer.observe(document.body, { childList: true, subtree: true }); } // ========== 7. 主流程 ========== async function main() { console.log('🎯 脚本已启动,正在使用精确选择器监测“确定”按钮…'); if (document.readyState === 'loading') { await new Promise(resolve => document.addEventListener('DOMContentLoaded', resolve, { once: true })); } // 同时启动视频循环监控(不阻塞主流程) enableVideoAutoReplay(); const confirmBtn = await waitForConfirmButton(); if (!confirmBtn) { console.warn('⏱️ 超时未找到“确定”按钮,脚本退出。'); return; } // 模拟看见弹窗后的反应时间 await sleep(rand(700, 1800)); console.log('🖱️ 准备拟人点击…'); await humanClick(confirmBtn); console.log('🎉 操作完成!'); } // 启动 main(); })();