// ==UserScript== // @name 江开学习助手 // @version 6.3 // @description 解除江苏开放大学学习平台答题页面的复制粘贴限制(支持复制、粘贴、剪切) // @author 天剑 // @match *://xuexi.jsou.cn/* // @match *://*.jsou.cn/* // @grant none // @run-at document-start // ==/UserScript== /* ============================================ * 版权声明 * -------------------------------------------- * 作者:天剑 * 公众号:江开搜题(微信扫描脚本横幅二维码即可关注) * 最新版本请通过公众号渠道获取,转载请保留本声明 * ============================================ */ (function() { 'use strict'; // 调试开关:默认关闭,不在浏览器控制台输出任何信息 const DEBUG = false; function log() { if (DEBUG) { console.log.apply(console, arguments); } } log('v6.3 脚本已启动'); // === 1. 禁止显示"禁止粘贴"和"复制成功"提示 === // 需要过滤的关键词 const filterKeywords = ['禁止粘贴', '复制成功']; function shouldFilter(msg) { return typeof msg === 'string' && filterKeywords.some(k => msg.includes(k)); } // 拦截 layer.msg const originalLayerMsg = window.layer && window.layer.msg; if (originalLayerMsg) { window.layer.msg = function(msg, options, callback) { if (shouldFilter(msg)) { log('[debug] 已阻止 layer.msg:', msg); return; } return originalLayerMsg.call(this, msg, options, callback); }; } // 拦截 alert const originalAlert = window.alert; window.alert = function(msg) { if (shouldFilter(msg)) { log('[debug] 已阻止 alert:', msg); return; } return originalAlert.call(window, msg); }; // 拦截 layer 的所有提示方法 if (window.layer) { const layerMethods = ['tips', 'toast', 'open', 'confirm', 'prompt', 'notify']; layerMethods.forEach(method => { if (window.layer[method]) { const original = window.layer[method]; window.layer[method] = function() { // 检查第一个参数(通常是消息) if (arguments[0] && shouldFilter(arguments[0])) { log('[debug] 已阻止 layer.' + method + ':', arguments[0]); return; } // 检查 options 中的 content if (arguments[1] && typeof arguments[1] === 'object' && arguments[1].content && shouldFilter(arguments[1].content)) { log('[debug] 已阻止 layer.' + method + ':', arguments[1].content); return; } return original.apply(this, arguments); }; } }); } // 拦截可能的 DOM 提示元素创建 function removeFilteredElements(root) { if (!root) return; const elements = root.querySelectorAll('*'); elements.forEach(function(el) { const text = el.textContent || el.innerText || ''; if (shouldFilter(text) && el.children.length === 0) { log('[debug] 已移除提示元素:', text); el.remove(); } }); } const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { mutation.addedNodes.forEach(function(node) { if (node.nodeType === 1) { // Element node // 检查当前元素 const text = node.textContent || node.innerText || ''; if (shouldFilter(text)) { log('[debug] 已移除提示元素:', text); node.remove(); return; } // 检查子元素 removeFilteredElements(node); } }); }); }); // 开始观察 DOM 变化 setTimeout(function() { if (document.body) { observer.observe(document.body, { childList: true, subtree: true }); } }, 1000); // 定期检查并移除已有的提示元素 setInterval(function() { removeFilteredElements(document.body); // 检查 iframe 中的提示 const iframes = document.querySelectorAll('iframe'); iframes.forEach(function(iframe) { try { if (iframe.contentDocument && iframe.contentDocument.body) { removeFilteredElements(iframe.contentDocument.body); } } catch (e) {} }); }, 500); // === 2. 核心复制功能 === function copySelection() { let selection = window.getSelection(); let selectedText = selection.toString(); // 如果主文档没有选中文本,尝试从iframe中获取 if (!selectedText) { const iframes = document.querySelectorAll('iframe'); for (let i = 0; i < iframes.length; i++) { try { const iframeDoc = iframes[i].contentDocument || iframes[i].contentWindow.document; if (iframeDoc && iframeDoc.getSelection) { selectedText = iframeDoc.getSelection().toString(); if (selectedText) break; } // 尝试从iframe的window获取选择 if (!selectedText && iframes[i].contentWindow && iframes[i].contentWindow.getSelection) { selectedText = iframes[i].contentWindow.getSelection().toString(); if (selectedText) break; } } catch (e) {} } } if (selectedText) { // 方法1: 使用 Clipboard API if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(selectedText).then(function() { log('[debug] 复制成功(Clipboard API)'); }).catch(function(err) { log('[debug] Clipboard API失败,使用fallback:', err); fallbackCopy(selectedText); }); } else { fallbackCopy(selectedText); } return true; } return false; } function fallbackCopy(text) { // 创建临时textarea来复制 const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; textarea.style.opacity = '0'; textarea.style.left = '-9999px'; document.body.appendChild(textarea); textarea.focus(); textarea.select(); try { const success = document.execCommand('copy'); log('[debug] 复制' + (success ? '成功' : '失败') + '(execCommand)'); } catch (e) { log('[debug] 复制失败:', e); } document.body.removeChild(textarea); } // === 3. 拦截 keydown 事件(捕获阶段) === function handleKeyDown(e) { // Ctrl+C (67), Ctrl+V (86), Ctrl+X (88), Ctrl+A (65) if (e.ctrlKey && (e.keyCode === 67 || e.keyCode === 86 || e.keyCode === 88 || e.keyCode === 65)) { // 阻止事件传播到其他监听器 e.stopImmediatePropagation(); e.stopPropagation(); e.preventDefault(); if (e.keyCode === 67) { // Ctrl+C: 复制 copySelection(); } else if (e.keyCode === 86) { // Ctrl+V: 粘贴 - 让浏览器默认处理 // 不需要额外操作,因为我们已经阻止了页面的拦截 } else if (e.keyCode === 88) { // Ctrl+X: 剪切 if (copySelection()) { // 删除选中文本需要额外处理 } } // Ctrl+A: 全选 - 让浏览器默认处理 } } // 在主document和window上添加捕获阶段监听器 document.addEventListener('keydown', handleKeyDown, true); window.addEventListener('keydown', handleKeyDown, true); document.addEventListener('keyup', function(e) { if (e.ctrlKey && (e.keyCode === 67 || e.keyCode === 86 || e.keyCode === 88 || e.keyCode === 65)) { e.stopImmediatePropagation(); } }, true); // === 4. 拦截 window.onkeydown 赋值 === let _onkeydown = null; let _documentOnkeydown = null; try { // 拦截 window.onkeydown Object.defineProperty(window, 'onkeydown', { get: function() { return _onkeydown; }, set: function(handler) { if (handler) { _onkeydown = function(event) { // 如果是Ctrl+C/V/X/A,不调用原始handler if (event.ctrlKey && (event.keyCode === 67 || event.keyCode === 86 || event.keyCode === 88 || event.keyCode === 65)) { log('[debug] 已拦截 onkeydown 阻止'); return true; // 返回true表示允许默认行为 } return handler.call(this, event); }; _onkeydown._wrapped = true; } else { _onkeydown = null; } }, configurable: true }); // 拦截 document.onkeydown Object.defineProperty(document, 'onkeydown', { get: function() { return _documentOnkeydown; }, set: function(handler) { if (handler) { _documentOnkeydown = function(event) { if (event.ctrlKey && (event.keyCode === 67 || event.keyCode === 86 || event.keyCode === 88 || event.keyCode === 65)) { log('[debug] 已拦截 document.onkeydown 阻止'); return true; } return handler.call(this, event); }; _documentOnkeydown._wrapped = true; } else { _documentOnkeydown = null; } }, configurable: true }); } catch (e) { log('[debug] defineProperty失败,使用轮询方式:', e); } // 轮询检查并覆盖 onkeydown(作为后备方案) setInterval(function() { // 检查 window.onkeydown if (window.onkeydown && !window.onkeydown._wrapped) { const original = window.onkeydown; window.onkeydown = function(event) { if (event.ctrlKey && (event.keyCode === 67 || event.keyCode === 86 || event.keyCode === 88 || event.keyCode === 65)) { return true; } return original.call(this, event); }; window.onkeydown._wrapped = true; log('[debug] 已覆盖 window.onkeydown'); } // 检查 document.onkeydown if (document.onkeydown && !document.onkeydown._wrapped) { const original = document.onkeydown; document.onkeydown = function(event) { if (event.ctrlKey && (event.keyCode === 67 || event.keyCode === 86 || event.keyCode === 88 || event.keyCode === 65)) { return true; } return original.call(this, event); }; document.onkeydown._wrapped = true; log('[debug] 已覆盖 document.onkeydown'); } }, 100); // === 5. 覆盖 document.execCommand 以确保复制命令工作 === const originalExecCommand = document.execCommand; document.execCommand = function(command) { if (command === 'copy') { // 获取当前选中文本 const selection = window.getSelection(); const selectedText = selection.toString(); if (selectedText && navigator.clipboard) { navigator.clipboard.writeText(selectedText); return true; } } return originalExecCommand.apply(document, arguments); }; // === 6. 拦截 UEditor iframe 内的事件 === let hooked = false; function tryHook() { if (hooked) return; if (typeof UE === 'undefined' || !UE.getEditor || !UE.EventBase) { setTimeout(tryHook, 10); return; } hooked = true; log('[debug] 已Hook UEditor'); // 拦截 addListener 阻止 beforepaste 被添加 const protoAddListener = UE.EventBase.prototype.addListener; UE.EventBase.prototype.addListener = function(types, listener) { if (types && types.indexOf('beforepaste') !== -1) { log('[debug] 已阻止添加 beforepaste 监听器'); return; } return protoAddListener.apply(this, arguments); }; // 拦截 fireEvent 确保 beforepaste 不会清空内容 const protoFireEvent = UE.EventBase.prototype.fireEvent; UE.EventBase.prototype.fireEvent = function() { const type = arguments[0]; if (type === 'beforepaste' && arguments[1] && arguments[1].html !== undefined) { const html = arguments[1]; const originalHtml = html.html; const result = protoFireEvent.apply(this, arguments); // 如果页面代码清空了html,恢复它 if (!html.html && originalHtml) { html.html = originalHtml; } return result; } return protoFireEvent.apply(this, arguments); }; // 拦截 getEditor 来修改编辑器实例 const originalGetEditor = UE.getEditor; UE.getEditor = function() { const editor = originalGetEditor.apply(this, arguments); protoAddListener.call(editor, 'ready', function() { log('[debug] 编辑器已就绪,移除限制'); // 移除 beforepaste 监听器 if (this._listeners) { this._listeners['beforepaste'] = []; } // 移除 paste 命令中的禁止粘贴提示 if (this.commands && this.commands['paste']) { const pasteCmd = this.commands['paste']; if (pasteCmd.execCommand && pasteCmd.execCommand.toString().includes('禁止粘贴')) { delete pasteCmd.execCommand; } } // 处理iframe内部 try { const iframe = this.iframe; if (iframe) { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; const iframeWindow = iframe.contentWindow; // 在iframe的document和window上添加捕获阶段监听器 iframeDoc.addEventListener('keydown', handleKeyDown, true); iframeWindow.addEventListener('keydown', handleKeyDown, true); iframeDoc.addEventListener('keyup', function(e) { if (e.ctrlKey && (e.keyCode === 67 || e.keyCode === 86 || e.keyCode === 88 || e.keyCode === 65)) { e.stopImmediatePropagation(); } }, true); // 覆盖iframe的execCommand const originalIframeExecCommand = iframeDoc.execCommand; iframeDoc.execCommand = function(command) { if (command === 'copy') { const selection = iframeDoc.getSelection ? iframeDoc.getSelection() : iframeWindow.getSelection(); const selectedText = selection.toString(); if (selectedText && navigator.clipboard) { navigator.clipboard.writeText(selectedText); return true; } } return originalIframeExecCommand.apply(iframeDoc, arguments); }; // 拦截iframe的onkeydown try { let _iframeOnkeydown = null; Object.defineProperty(iframeWindow, 'onkeydown', { get: function() { return _iframeOnkeydown; }, set: function(handler) { if (handler) { _iframeOnkeydown = function(event) { if (event.ctrlKey && (event.keyCode === 67 || event.keyCode === 86 || event.keyCode === 88 || event.keyCode === 65)) { return true; } return handler.call(this, event); }; } else { _iframeOnkeydown = null; } }, configurable: true }); let _iframeDocOnkeydown = null; Object.defineProperty(iframeDoc, 'onkeydown', { get: function() { return _iframeDocOnkeydown; }, set: function(handler) { if (handler) { _iframeDocOnkeydown = function(event) { if (event.ctrlKey && (event.keyCode === 67 || event.keyCode === 86 || event.keyCode === 88 || event.keyCode === 65)) { return true; } return handler.call(this, event); }; } else { _iframeDocOnkeydown = null; } }, configurable: true }); } catch (e) { log('[debug] iframe defineProperty失败:', e); } } } catch (e) { log('[debug] iframe处理失败:', e); } }); return editor; }; // 定期检查并清除 beforepaste 监听器 setInterval(function() { if (UE.instants) { for (var key in UE.instants) { var editor = UE.instants[key]; if (editor && editor._listeners && editor._listeners['beforepaste']) { if (editor._listeners['beforepaste'].length > 0) { editor._listeners['beforepaste'] = []; } } } } }, 200); } tryHook(); // === 7. 页面底部固定宣传横幅 === (function addPromotionBanner() { // 检查是否已关闭(使用 localStorage 记录关闭状态) if (localStorage.getItem('jsou_banner_closed') === 'true') { return; } // 创建横幅样式 const style = document.createElement('style'); style.textContent = ` #jsou-promo-banner { position: fixed; bottom: 0; left: 0; right: 0; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #fff; padding: 12px 20px; z-index: 999999; display: flex; align-items: center; justify-content: center; font-size: 14px; box-shadow: 0 -2px 10px rgba(0,0,0,0.2); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } #jsou-promo-banner .promo-content { display: flex; align-items: center; gap: 15px; } #jsou-promo-banner .promo-text { display: flex; align-items: center; gap: 8px; } #jsou-promo-banner .promo-icon { font-size: 20px; } #jsou-promo-banner .promo-qrcode { width: 60px; height: 60px; background: #fff; border-radius: 4px; display: flex; align-items: center; justify-content: center; color: #333; font-size: 12px; text-align: center; line-height: 1.2; } #jsou-promo-banner .promo-close { position: absolute; right: 15px; top: 50%; transform: translateY(-50%); background: rgba(255,255,255,0.2); border: none; color: #fff; width: 28px; height: 28px; border-radius: 50%; cursor: pointer; font-size: 18px; display: flex; align-items: center; justify-content: center; transition: background 0.3s; } #jsou-promo-banner .promo-close:hover { background: rgba(255,255,255,0.3); } #jsou-promo-banner .promo-btn { background: #fff; color: #667eea; border: none; padding: 6px 16px; border-radius: 20px; cursor: pointer; font-weight: bold; font-size: 13px; transition: transform 0.2s; } #jsou-promo-banner .promo-btn:hover { transform: scale(1.05); } /* 为横幅留出底部空间 */ body { padding-bottom: 60px !important; } /* 二维码弹窗 */ #jsou-qr-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: 1000000; display: flex; align-items: center; justify-content: center; } #jsou-qr-modal { background: #fff; border-radius: 12px; padding: 24px 32px; text-align: center; box-shadow: 0 4px 20px rgba(0,0,0,0.3); } #jsou-qr-modal .qr-title { font-weight: bold; font-size: 16px; color: #333; } #jsou-qr-modal img { width: 220px; height: 220px; display: block; margin: 14px auto; } #jsou-qr-modal .qr-tip { color: #999; font-size: 12px; } `; document.head.appendChild(style); // 创建横幅元素 const banner = document.createElement('div'); banner.id = 'jsou-promo-banner'; banner.innerHTML = `
📱 关注公众号【江开搜题】,获取更多学习资料和答题技巧
关注公众号
`; // 等待页面加载完成后添加横幅 function appendBanner() { if (document.body) { document.body.appendChild(banner); bindEvents(); } else { setTimeout(appendBanner, 100); } } appendBanner(); // 绑定事件 function bindEvents() { // 关闭按钮 document.getElementById('jsou-promo-close').addEventListener('click', function() { banner.style.display = 'none'; // 记录关闭状态,24小时内不再显示 localStorage.setItem('jsou_banner_closed', 'true'); setTimeout(() => { localStorage.removeItem('jsou_banner_closed'); }, 24 * 60 * 60 * 1000); }); // 关注按钮:弹出大尺寸二维码 document.getElementById('jsou-promo-follow').addEventListener('click', showQrModal); } // 弹出大尺寸二维码弹窗(二维码直接复用横幅中已内嵌的图片,无需外部链接) function showQrModal() { let mask = document.getElementById('jsou-qr-mask'); if (mask) { mask.style.display = 'flex'; return; } mask = document.createElement('div'); mask.id = 'jsou-qr-mask'; const modal = document.createElement('div'); modal.id = 'jsou-qr-modal'; const title = document.createElement('div'); title.className = 'qr-title'; title.textContent = '微信扫码关注公众号【江开搜题】'; const qrImg = banner.querySelector('.promo-qrcode img').cloneNode(true); const tip = document.createElement('div'); tip.className = 'qr-tip'; tip.textContent = '打开微信扫一扫,点击空白处关闭'; modal.appendChild(title); modal.appendChild(qrImg); modal.appendChild(tip); mask.appendChild(modal); document.body.appendChild(mask); mask.addEventListener('click', function() { mask.style.display = 'none'; }); modal.addEventListener('click', function(e) { e.stopPropagation(); }); } })(); })();