// ==UserScript== // @name 超星复制粘贴助手 (Chaoxing Copy&Paste Helper) // @namespace https://github.com/iPycc/Chaoxing-Copy&Paste-Helper // @version 2.1 // @description 超星网页端字体解密+强制启用粘贴功能,支持复制题目、UEditor富文本编辑器和所有输入元素 // @author iPycc & wyn665817 // @match *://*.chaoxing.com/* // @require https://scriptcat.org/lib/668/1.0/TyprMd5.js // @resource Table https://www.forestpolice.org/ttf/2.0/table.json // @run-at document-start // @grant unsafeWindow // @grant GM_getResourceText // @license MIT // ==/UserScript== (function() { 'use strict'; const ChaoxingHelper = { // 复制 fontDecryptor: { decrypt() { const styleElement = this.findStyleContaining('font-cxsecret'); if (!styleElement) return; const fontMatch = styleElement.textContent.match(/base64,([\w\W]+?)'/); if (!fontMatch) return; const fontData = Typr.parse(this.base64ToUint8Array(fontMatch[1]))[0]; const table = JSON.parse(GM_getResourceText('Table')); const charMap = this.createCharMap(fontData, table); this.replaceEncryptedText(charMap); console.log(`[Chaoxing Helper] Decryption completed ${Object.keys(charMap).length} characters`); }, findStyleContaining(text) { const styles = document.querySelectorAll('style'); return Array.from(styles).find(style => style.textContent.includes(text) ); }, base64ToUint8Array(base64) { const data = window.atob(base64); return new Uint8Array([...data].map(char => char.charCodeAt(0))); }, createCharMap(font, table) { const charMap = {}; for (let i = 19968; i < 40870; i++) { const glyph = Typr.U.codeToGlyph(font, i); if (!glyph) continue; const path = Typr.U.glyphToPath(font, glyph); const pathHash = md5(JSON.stringify(path)).slice(24); if (table[pathHash]) { charMap[String.fromCharCode(i)] = String.fromCharCode(table[pathHash]); } } return charMap; }, replaceEncryptedText(charMap) { const elements = document.querySelectorAll('.font-cxsecret'); elements.forEach(element => { let html = element.innerHTML; Object.entries(charMap).forEach(([encrypted, decrypted]) => { html = html.replace(new RegExp(encrypted, 'g'), decrypted); }); element.innerHTML = html; element.classList.remove('font-cxsecret'); }); } }, // 粘贴 pasteEnabler: { init() { this.removeGlobalRestrictions(); this.injectGlobalStyles(); this.enableExistingElements(); this.startMutationObserver(); this.monitorUEditor(); }, removeGlobalRestrictions() { ['paste', 'contextmenu', 'selectstart', 'dragstart', 'copy', 'cut', 'keydown'] .forEach(event => document[`on${event}`] = null); this.removeElementRestrictions(document.body); }, injectGlobalStyles() { const style = document.createElement('style'); style.textContent = ` * { -webkit-user-select: text !important; -moz-user-select: text !important; -ms-user-select: text !important; user-select: text !important; } input, textarea, [contenteditable] { user-select: text !important; pointer-events: auto !important; } `; document.head.appendChild(style); }, removeElementRestrictions(element) { if (!element) return; // 移除事件监听器和属性 ['paste', 'contextmenu', 'keydown', 'selectstart', 'dragstart', 'copy', 'cut'] .forEach(event => { element[`on${event}`] = null; element.removeAttribute(`on${event}`); }); element.removeAttribute('readonly'); element.removeAttribute('disabled'); // 恢复样式 Object.assign(element.style, { userSelect: 'text', webkitUserSelect: 'text', mozUserSelect: 'text', msUserSelect: 'text', pointerEvents: 'auto' }); }, enableExistingElements() { document.querySelectorAll('input, textarea, [contenteditable]') .forEach(el => this.removeElementRestrictions(el)); }, startMutationObserver() { new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType !== 1) return; if (node.matches?.('input, textarea, [contenteditable]')) { this.removeElementRestrictions(node); } if (node.tagName === 'IFRAME') { setTimeout(() => { try { node.contentDocument?.querySelectorAll('input, textarea, [contenteditable]') .forEach(el => this.removeElementRestrictions(el)); } catch (e) { // 跨域iframe,静默处理 } }, 500); } node.querySelectorAll?.('input, textarea, [contenteditable]') .forEach(el => this.removeElementRestrictions(el)); }); }); }).observe(document.body, { childList: true, subtree: true }); }, handlePaste(e) { e.preventDefault(); e.stopPropagation(); const doc = e.target.ownerDocument || document; const clipboardData = e.clipboardData || window.clipboardData; // 处理图片粘贴 const imageItem = Array.from(clipboardData?.items || []) .find(item => item.type?.startsWith('image/')); if (imageItem) { const imageFile = imageItem.getAsFile(); const reader = new FileReader(); reader.onload = () => this.insertImage(doc, reader.result); reader.readAsDataURL(imageFile); return; } // 处理文本粘贴 const text = clipboardData?.getData?.('text/plain') || window.clipboardData?.getData?.('Text') || ''; if (text) { this.insertText(doc, text); } }, insertImage(doc, dataUrl) { const img = doc.createElement('img'); img.src = dataUrl; img.alt = '粘贴图片'; const selection = doc.getSelection(); if (selection?.rangeCount > 0) { const range = selection.getRangeAt(0); range.deleteContents(); range.insertNode(img); range.setStartAfter(img); selection.removeAllRanges(); selection.addRange(range); } else { doc.body?.appendChild(img); } }, insertText(doc, text) { if (doc.execCommand?.('insertText', false, text)) return true; const selection = doc.getSelection(); if (selection?.rangeCount > 0) { const range = selection.getRangeAt(0); range.deleteContents(); const textNode = doc.createTextNode(text); range.insertNode(textNode); range.setStartAfter(textNode); selection.removeAllRanges(); selection.addRange(range); return true; } doc.body?.appendChild(doc.createTextNode(text)); return false; }, monitorUEditor() { let attempts = 0; const maxAttempts = 20; const checkUEditor = () => { attempts++; // 处理主窗口UEditor实例 if (typeof UE !== 'undefined' && UE.instants) { Object.values(UE.instants).forEach(instance => { this.processUEditorInstance(instance); }); } // 处理iframe中的UEditor实例 document.querySelectorAll('iframe').forEach(iframe => { try { const iframeWindow = iframe.contentWindow; if (iframeWindow?.UE?.instants) { Object.values(iframeWindow.UE.instants).forEach(instance => { this.processUEditorInstance(instance); }); } } catch (error) { // 跨域iframe,静默处理 } }); if (attempts < maxAttempts) setTimeout(checkUEditor, 500); }; checkUEditor(); }, processUEditorInstance(instance) { if (!instance || typeof instance !== 'object') return; // 启用编辑器 if (instance.options) { instance.options.pasteplain = true; instance.options.readonly = false; instance.options.disabled = false; } if (instance.setEnabled && instance.body) { instance.setEnabled(true); } // 处理编辑器主体 if (instance.body && !instance.body.__cxPasteEnabled) { this.removeElementRestrictions(instance.body); instance.body.addEventListener('paste', (e) => this.handlePaste(e), true); instance.body.__cxPasteEnabled = true; } // 处理iframe文档 const iframeDoc = instance.iframe?.contentDocument; if (iframeDoc && !iframeDoc.__cxPasteEnabled) { this.removeElementRestrictions(iframeDoc.body); iframeDoc.addEventListener('paste', (e) => this.handlePaste(e), true); iframeDoc.__cxPasteEnabled = true; } } }, // 主初始化 init() { // console.log('[Chaoxing Helper] 开始初始化'); this.pasteEnabler.init(); this.fontDecryptor.decrypt(); console.log('[Chaoxing Helper] Done'); } }; // 启动助手 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => ChaoxingHelper.init()); } else { ChaoxingHelper.init(); } })();