// ==UserScript== // @name 超星/网课专用 PDF 提取器 (更新含移除遮挡功能) // @namespace http://tampermonkey.net/ // @version 3.1 // @description 自动录屏/截图并将网课课件/题目转换为 PDF,支持一键移除遮挡弹窗 // @author YourName // @match *://*.chaoxing.com/* // @match *://*.zhihuishu.com/* // @match *://*.icve.com.cn/* // @match *://*.edu.cn/* // @include * // @run-at document-end // @grant GM_registerMenuCommand // @grant GM_addStyle // @require https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js // ==/UserScript== (function() { 'use strict'; // ========================================== // 1. UI 部分:创建悬浮按钮组 // ========================================== function createFloatingButtons() { const container = document.createElement('div'); container.id = 'pdf-extractor-container'; container.style.cssText = ` position: fixed; bottom: 20px; right: 20px; z-index: 999999; display: flex; flex-direction: column; gap: 10px; `; // --- 按钮 1: PDF 提取 (绿色) --- const extractBtn = document.createElement('button'); extractBtn.id = 'pdf-extractor-btn'; extractBtn.innerText = 'PDF提取'; styleButton(extractBtn, '#4CAF50'); // Green extractBtn.onclick = (e) => { e.stopPropagation(); enableSelectionMode(); }; // --- 按钮 2: 移除遮挡 (红色) --- const removeBtn = document.createElement('button'); removeBtn.id = 'element-remover-btn'; removeBtn.innerText = '✕ 移除遮挡'; styleButton(removeBtn, '#f44336'); // Red removeBtn.onclick = (e) => { e.stopPropagation(); enableRemoverMode(); }; container.appendChild(removeBtn); container.appendChild(extractBtn); document.body.appendChild(container); } function styleButton(btn, color) { btn.style.cssText = ` background-color: ${color}; color: white; padding: 10px 15px; border: none; border-radius: 50px; cursor: pointer; box-shadow: 0 4px 6px rgba(0,0,0,0.2); font-family: Arial, sans-serif; font-size: 14px; transition: transform 0.2s, background-color 0.2s; white-space: nowrap; `; btn.onmouseover = () => btn.style.transform = 'scale(1.05)'; btn.onmouseout = () => btn.style.transform = 'scale(1)'; } // 初始化 UI createFloatingButtons(); // ========================================== // 2. 核心功能 A:移除遮挡 (新增功能) // ========================================== let isRemoverMode = false; let lastRemoverHighlight = null; function enableRemoverMode() { if (isRemoverMode) return; isRemoverMode = true; const btn = document.getElementById('element-remover-btn'); if(btn) { btn.innerText = '请点击要删除的元素...'; btn.style.backgroundColor = '#FF9800'; // Orange warning } document.addEventListener('mouseover', highlightRemoverElement, true); document.addEventListener('click', executeRemoveElement, true); showToast("【移除模式】\n点击那个挡住你的弹窗或背景\n(点击后立即删除)"); } function highlightRemoverElement(e) { // 忽略我们的控制按钮 if (document.getElementById('pdf-extractor-container').contains(e.target)) return; if (lastRemoverHighlight) lastRemoverHighlight.style.outline = ''; e.target.style.outline = '4px dashed red'; // 虚线红色表示删除 e.target.style.cursor = 'crosshair'; lastRemoverHighlight = e.target; } function executeRemoveElement(e) { // 忽略点击按钮本身 if (document.getElementById('pdf-extractor-container').contains(e.target)) return; e.preventDefault(); e.stopPropagation(); const target = e.target; // 停止监听 document.removeEventListener('mouseover', highlightRemoverElement, true); document.removeEventListener('click', executeRemoveElement, true); if (lastRemoverHighlight) lastRemoverHighlight.style.outline = ''; // 删除元素 if(target) { target.remove(); showToast("已删除遮挡元素!"); } // 恢复按钮状态 isRemoverMode = false; const btn = document.getElementById('element-remover-btn'); if(btn) { btn.innerText = '✕ 移除遮挡'; btn.style.backgroundColor = '#f44336'; } } // ========================================== // 3. 核心功能 B:PDF 提取逻辑 (原逻辑) // ========================================== let isSelectionMode = false; let lastHighlighted = null; function enableSelectionMode() { if (isSelectionMode) return; isSelectionMode = true; const btn = document.getElementById('pdf-extractor-btn'); if(btn) btn.innerText = '请点击课件区域'; if(btn) btn.style.backgroundColor = '#2196F3'; // Blue document.addEventListener('mouseover', highlightElement, true); document.addEventListener('click', startCaptureProcess, true); showToast("【提取模式】\n请点击你的 PPT/课件滚动区域!"); } function highlightElement(e) { if (document.getElementById('pdf-extractor-container').contains(e.target)) return; if (lastHighlighted) lastHighlighted.style.outline = ''; e.target.style.outline = '4px solid #4CAF50'; // 实线绿色表示提取 lastHighlighted = e.target; } function showToast(msg) { const toast = document.createElement('div'); toast.innerText = msg; toast.style.cssText = ` position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.85); color: #fff; padding: 12px 24px; border-radius: 8px; z-index: 1000000; text-align: center; font-size: 14px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); pointer-events: none; `; document.body.appendChild(toast); setTimeout(() => toast.remove(), 3500); } // --- 滚动与截图逻辑 --- function findScrollParent(element) { let el = element; while (el && el !== document.body && el !== document.documentElement) { const style = window.getComputedStyle(el); const overflowY = style.overflowY; if ((overflowY === 'auto' || overflowY === 'scroll') && el.scrollHeight > el.clientHeight) { return el; } el = el.parentElement; } return document.documentElement; } async function captureVisibleArea() { const container = document.getElementById('pdf-extractor-container'); if(container) container.style.display = 'none'; // 截图时隐藏所有按钮 try { const canvas = await html2canvas(document.body, { useCORS: true, allowTaint: true, scrollX: window.scrollX, scrollY: window.scrollY, windowWidth: window.innerWidth, windowHeight: window.innerHeight, x: window.scrollX, y: window.scrollY, width: window.innerWidth, height: window.innerHeight, ignoreElements: (element) => element.id === 'pdf-extractor-container' }); if(container) container.style.display = 'flex'; return canvas.toDataURL('image/jpeg', 0.9); } catch (e) { console.error("截图失败", e); if(container) container.style.display = 'flex'; return null; } } function getImageDimensions(src) { return new Promise((resolve) => { const img = new Image(); img.onload = () => { resolve({ width: img.width, height: img.height }); }; img.src = src; }); } async function startCaptureProcess(e) { e.preventDefault(); e.stopPropagation(); document.removeEventListener('mouseover', highlightElement, true); document.removeEventListener('click', startCaptureProcess, true); if (lastHighlighted) lastHighlighted.style.outline = ''; isSelectionMode = false; const btn = document.getElementById('pdf-extractor-btn'); const scrollContainer = findScrollParent(e.target); if (!scrollContainer) { alert("未找到可滚动的区域,请重试"); if(btn) { btn.innerText = 'PDF提取'; btn.style.backgroundColor = '#4CAF50'; } return; } if(btn) { btn.innerText = '处理中...'; btn.style.backgroundColor = '#999'; btn.disabled = true; } await runScreenCapture(scrollContainer); if(btn) { btn.innerText = 'PDF提取'; btn.style.backgroundColor = '#4CAF50'; btn.disabled = false; } } async function runScreenCapture(container) { alert("开始处理!\n请确保已‘移除遮挡’,否则会被录入。\n请不要移动鼠标。"); const isWindowScroll = (container === document.documentElement || container === document.body); const totalHeight = container.scrollHeight; const viewHeight = isWindowScroll ? window.innerHeight : container.clientHeight; let currentScroll = 0; let pageIndex = 0; let pdf = null; const originalOverflow = container.style.overflow; if(!isWindowScroll) container.style.overflow = 'hidden'; else document.documentElement.style.overflow = 'hidden'; try { const { jsPDF } = window.jspdf; while (currentScroll < totalHeight) { if (isWindowScroll) window.scrollTo(0, currentScroll); else container.scrollTop = currentScroll; await new Promise(r => setTimeout(r, 1500)); console.log(`正在截取第 ${pageIndex + 1} 页...`); const imgData = await captureVisibleArea(); if (!imgData) { currentScroll += viewHeight; continue; } const imgProps = await getImageDimensions(imgData); const imgWidth = imgProps.width; const imgHeight = imgProps.height; const orientation = imgWidth > imgHeight ? 'l' : 'p'; if (pageIndex === 0) { pdf = new jsPDF({ orientation: orientation, unit: 'px', format: [imgWidth, imgHeight] }); } else { pdf.addPage([imgWidth, imgHeight], orientation); } pdf.addImage(imgData, 'JPEG', 0, 0, imgWidth, imgHeight); currentScroll += viewHeight; pageIndex++; } if(!isWindowScroll) container.style.overflow = originalOverflow; else document.documentElement.style.overflow = originalOverflow; if (pdf) { pdf.save(`课件提取_${Date.now()}.pdf`); alert("下载完成!"); } else { alert("错误:未获取到内容"); } } catch (err) { console.error(err); alert("出错: " + err.message); if(!isWindowScroll) container.style.overflow = originalOverflow; else document.documentElement.style.overflow = originalOverflow; } } })();