您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
只有直播可用。把PPT拖至最大的窗口。
// ==UserScript== // @name 东南大学直播课PPT自动截图 // @namespace http://tampermonkey.net/ // @version 1.1 // @description 只有直播可用。把PPT拖至最大的窗口。 // @author Vic and his AI // @license MIT // @match https://cvs.seu.edu.cn/* // @require https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js // @grant GM_download // @grant GM_registerMenuCommand // ==/UserScript== (function() { 'use strict'; console.log('脚本已加载 - 东大直播课PPT自动截图'); // 配置参数 let CONFIG = { targetSelector: '.player-view.player-view-1', // PPT容器选择器 checkInterval: 20000, // 检查间隔20秒 pixelTolerance: 0.05 // 像素容忍度,这里设置为5% }; // 状态管理 let state = { isMonitoring: false, intervalId: null, screenshotHistory: [] }; // 初始化UI function initUI() { const ui = document.createElement('div'); ui.id = 'ppt-monitor-ui'; ui.style.cssText = ` position: fixed; top: 50%; left: 0; transform: translateY(-50%); background: rgba(40,40,40,0.93); color: #fff; padding: 10px; border-radius: 0 8px 8px 0; box-shadow: 0 4px 12px rgba(0,0,0,0.25); z-index: 99999; font-family: Arial, sans-serif; width: 50px; min-width: 50px; overflow: hidden; transition: width 0.3s ease; `; // UI内容 ui.innerHTML = ` <div class="expanded-content" style="display:none;"> <h3 style="margin:0 0 12px;font-weight:500;white-space:nowrap;">PPT监控器 v1.1</h3> <div style="margin-bottom:10px;white-space:nowrap;"> <button id="toggleMonitor" style="margin-right:8px;">启动监控</button> <button id="manualCapture">手动截图</button> </div> <div style="margin-bottom:10px;white-space:nowrap;"> <label for="intervalInput">截图间隔 (s): </label> <input type="number" id="intervalInput" value="${CONFIG.checkInterval / 1000}" min="1" style="width: 50px;"> </div> <div style="margin-bottom:10px;white-space:nowrap;"> <label for="toleranceInput">差异比例 (%): </label> <input type="number" id="toleranceInput" value="${CONFIG.pixelTolerance * 100}" min="1" max="100" style="width: 45px;"> </div> <div class="expanded-status" style="font-size:13px;white-space:nowrap;"> <p style="color:#0cf;margin:0;">监控已停止</p> </div> </div> <div class="collapsed-status" style=" position: absolute; top: 50%; left: 10px; transform: translateY(-50%); color: #0cf; font-size: 12px; white-space: nowrap; "> <p style="margin:0;">已停止</p> </div> <div class="collapse-indicator" style=" position: absolute; top: 50%; right: 5px; transform: translateY(-50%); color: #fff; font-size: 16px; ">▶</div> `; document.body.appendChild(ui); // 鼠标交互 ui.addEventListener('mouseenter', () => { ui.style.width = '160px'; ui.querySelector('.expanded-content').style.display = 'block'; ui.querySelector('.collapsed-status').style.display = 'none'; }); ui.addEventListener('mouseleave', () => { ui.style.width = '50px'; ui.querySelector('.expanded-content').style.display = 'none'; ui.querySelector('.collapsed-status').style.display = 'block'; }); // 按钮事件 ui.querySelector('#toggleMonitor').addEventListener('click', toggleMonitoring); ui.querySelector('#manualCapture').addEventListener('click', () => captureScreenshot('manual')); ui.querySelector('#intervalInput').addEventListener('input', setIntervalTime); ui.querySelector('#toleranceInput').addEventListener('input', setTolerance); } // 核心截图功能 async function captureScreenshot(source = 'auto') { try { const target = document.querySelector(CONFIG.targetSelector); if (!target) throw new Error('PPT容器未找到'); // 等待元素渲染 await new Promise(resolve => setTimeout(resolve, 500)); const canvas = await html2canvas(target, { useCORS: true, scale: 2, logging: false, backgroundColor: '#FFFFFF', onclone: (doc) => { // 处理特殊元素 doc.querySelectorAll('video, audio').forEach(media => media.pause()); } }); const timestamp = new Date().toISOString().replace(/[:T.]/g, '-').slice(0, 19); const filename = `SEU-PPT_${timestamp}_${source}.png`; const currentScreenshot = canvas.toDataURL('image/png'); // 检查是否重复 let isDuplicate = false; const lastScreenshot = state.screenshotHistory[state.screenshotHistory.length - 1]; if (lastScreenshot && await compareImages(lastScreenshot, currentScreenshot)) { isDuplicate = true; } if (!isDuplicate) { const link = document.createElement('a'); link.href = currentScreenshot; link.download = filename; link.click(); state.screenshotHistory.push(currentScreenshot); console.log(`截图成功: ${filename}`); } else { console.log(`重复截图,跳过: ${filename}`); } } catch (error) { console.error('[PPT监控器] 错误:', error); } } // 简单的图片对比函数 async function compareImages(image1, image2) { if (!image1 || !image2) return false; const canvas1 = document.createElement('canvas'); const ctx1 = canvas1.getContext('2d'); const img1 = new Image(); img1.src = image1; await new Promise(resolve => img1.onload = resolve); canvas1.width = img1.width; canvas1.height = img1.height; ctx1.drawImage(img1, 0, 0); const canvas2 = document.createElement('canvas'); const ctx2 = canvas2.getContext('2d'); const img2 = new Image(); img2.src = image2; await new Promise(resolve => img2.onload = resolve); canvas2.width = img2.width; canvas2.height = img2.height; ctx2.drawImage(img2, 0, 0); const data1 = ctx1.getImageData(0, 0, canvas1.width, canvas1.height).data; const data2 = ctx2.getImageData(0, 0, canvas2.width, canvas2.height).data; let differentPixelCount = 0; for (let i = 0; i < data1.length/10; i++) { if (data1[10 * i] !== data2[10 * i]) { differentPixelCount++; } } const totalPixelCount = data1.length / 10 / 4; // 每个像素由4个值(r, g, b, a)组成 const toleranceCount = totalPixelCount * CONFIG.pixelTolerance; return differentPixelCount <= toleranceCount; } // 监控状态控制 function startMonitoring() { const target = document.querySelector(CONFIG.targetSelector); if (!target) { updateStatus('error', 'PPT容器未找到'); return; } // 立即执行首次截图 captureScreenshot('auto'); state.intervalId = setInterval(() => { if (state.isMonitoring) captureScreenshot('auto'); }, CONFIG.checkInterval); state.isMonitoring = true; updateStatus('monitoring'); document.querySelector('#toggleMonitor').textContent = '停止监控'; } function stopMonitoring() { clearInterval(state.intervalId); state.isMonitoring = false; updateStatus('stopped'); document.querySelector('#toggleMonitor').textContent = '启动监控'; } function toggleMonitoring() { state.isMonitoring ? stopMonitoring() : startMonitoring(); } // 设置截图间隔 function setIntervalTime() { const input = document.querySelector('#intervalInput'); const newInterval = parseInt(input.value) * 1000; if (!isNaN(newInterval) && newInterval > 0) { CONFIG.checkInterval = newInterval; if (state.isMonitoring) { stopMonitoring(); startMonitoring(); } console.log(`截图间隔已设置为 ${newInterval / 1000} 秒`); } else { console.error('输入的间隔时间无效,请输入一个大于0的整数。'); } } // 设置像素容忍度 function setTolerance() { const input = document.querySelector('#toleranceInput'); const newTolerance = parseFloat(input.value) / 100; if (!isNaN(newTolerance) && newTolerance > 0 && newTolerance <= 1) { CONFIG.pixelTolerance = newTolerance; console.log(`像素容忍度已设置为 ${newTolerance * 100}%`); } else { console.error('输入的容忍度无效,请输入一个1到100之间的数字。'); } } // 状态显示更新 function updateStatus(type, message) { const elements = { expanded: document.querySelector('.expanded-status p'), collapsed: document.querySelector('.collapsed-status p') }; const statusMap = { monitoring: { expanded: '监控中...', collapsed: '已开启', color: '#0cf' }, stopped: { expanded: '监控已停止', collapsed: '已停止', color: '#0cf' }, error: { expanded: '错误: ' + message, collapsed: '错误', color: '#f00' } }; const status = statusMap[type] || { expanded: '未知状态', collapsed: '未知', color: '#0cf' }; elements.expanded.textContent = status.expanded; elements.expanded.style.color = status.color; elements.collapsed.textContent = status.collapsed; elements.collapsed.style.color = status.color; } // 启动流程 window.addEventListener('load', () => { initUI(); }); // 离开页面提醒 window.addEventListener('beforeunload', () => { if (state.isMonitoring) { return '截图进程正在进行中,确定要离开吗?'; } }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址