// ==UserScript== // @name 解除学习通网课复制粘贴的限制(自用) // @namespace http://tampermonkey.net/ // @version 1.0 // @description 解除网课复制粘贴限制(只测试超星可以,别的网站自测) // @author 独角 // @match *://*.zhihuishu.com/* // @match *://*.chaoxing.com/* // @match *://*.edu.cn/* // @match *://*.org.cn/* // @match *://*.xueyinonline.com/* // @match *://*.hnsyu.net/* // @match *://*.qutjxjy.cn/* // @match *://*.ynny.cn/* // @match *://*.hnvist.cn/* // @match *://*.fjlecb.cn/* // @match *://*.gdhkmooc.com/* // @match *://*.cugbonline.cn/* // @match *://*.zjelib.cn/* // @match *://*.cqrspx.cn/* // @match *://*.neauce.com/* // @match *://*.zhihui-yun.com/* // @match *://*.cqie.cn/* // @match *://*.ccqmxx.com/* // @match *://*.icve.com.cn/* // @match *://*.course.icve.com.cn/* // @match *://*.courshare.cn/* // @match *://*.webtrn.cn/* // @match *://*.zjy2.icve.com.cn/* // @match *://*.zyk.icve.com.cn/* // @match *://*.icourse163.org/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant none // ==/UserScript== (function() { 'use strict'; const CONFIG = { STORAGE_KEY: 'floatingToggleStatus', DRAG_THRESHOLD: 5, // 像素,判定为拖动的最小距离 ANIMATION_DURATION: 300, POSITION_STORAGE_KEY: 'floatingButtonPosition', CLICK_TIMEOUT: 300, REFRESH_NOTIFICATION_DURATION: 3000, REFRESH_NOTIFICATION_ANIMATION: 500, REFRESH_NOTIFICATION_OFFSET: 20 }; const state = { enabled: localStorage.getItem(CONFIG.STORAGE_KEY) !== 'false', isDragging: false, dragStart: { x: 0, y: 0 }, dragOffset: { x: 0, y: 0 }, position: JSON.parse(localStorage.getItem(CONFIG.POSITION_STORAGE_KEY) || 'null'), clickStartTime: 0, clickStartPos: { x: 0, y: 0 } }; let buttonContainer = null; let toggleButton = null; let statusIndicator = null; let refreshNotification = null; const originalEventMethods = { addEventListener: EventTarget.prototype.addEventListener, removeEventListener: EventTarget.prototype.removeEventListener }; const eventListeners = new WeakMap(); const styles = { container: ` position: fixed; z-index: 9999; transition: transform 0.3s ease; `, button: ` width: 50px; height: 50px; border-radius: 50%; color: white; border: none; box-shadow: 0 4px 15px rgba(0,0,0,0.2); cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 24px; transition: all 0.3s ease; `, indicator: ` background-color: white; color: #333; padding: 8px 15px; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin-bottom: 10px; margin-right: 5px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 14px; font-weight: 500; opacity: 0; transform: translateY(10px); transition: all 0.3s ease; white-space: nowrap; position: absolute; bottom: 100%; right: 0; `, refreshNotification: ` position: fixed; top: ${CONFIG.REFRESH_NOTIFICATION_OFFSET}px; left: 50%; transform: translateX(-50%) translateY(-100%); background-color: white; color: #333; padding: 12px 24px; border-radius: 6px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 16px; font-weight: 500; z-index: 10000; opacity: 0; transition: opacity ${CONFIG.REFRESH_NOTIFICATION_ANIMATION}ms cubic-bezier(0.25, 0.1, 0.25, 1), transform ${CONFIG.REFRESH_NOTIFICATION_ANIMATION}ms cubic-bezier(0.25, 0.1, 0.25, 1); pointer-events: none; ` }; function saveState() { localStorage.setItem(CONFIG.STORAGE_KEY, state.enabled); } function saveButtonPosition() { const position = { left: buttonContainer.style.left, top: buttonContainer.style.top }; localStorage.setItem(CONFIG.POSITION_STORAGE_KEY, JSON.stringify(position)); } function updateButtonState() { toggleButton.innerHTML = state.enabled ? '✓' : '×'; toggleButton.style.backgroundColor = state.enabled ? '#4a6cf7' : '#ff4d4f'; statusIndicator.textContent = `当前脚本状态:${state.enabled ? '开启' : '关闭'}`; } function showStatus(message) { statusIndicator.textContent = message; statusIndicator.style.opacity = '1'; statusIndicator.style.transform = 'translateY(0)'; setTimeout(() => { statusIndicator.style.opacity = '0'; statusIndicator.style.transform = 'translateY(10px)'; }, CONFIG.ANIMATION_DURATION); } function showRefreshNotification(message) { if (!refreshNotification) { refreshNotification = document.createElement('div'); refreshNotification.id = 'refresh-notification'; refreshNotification.style.cssText = styles.refreshNotification; document.body.appendChild(refreshNotification); } refreshNotification.style.opacity = '0'; refreshNotification.style.transform = 'translateX(-50%) translateY(-100%)'; refreshNotification.textContent = message; void refreshNotification.offsetWidth; refreshNotification.style.opacity = '1'; refreshNotification.style.transform = 'translateX(-50%) translateY(0)'; setTimeout(() => { refreshNotification.style.opacity = '0'; refreshNotification.style.transform = 'translateX(-50%) translateY(-100%)'; }, CONFIG.REFRESH_NOTIFICATION_DURATION); } function createFloatingButton() { buttonContainer = document.createElement('div'); buttonContainer.id = 'floating-toggle-container'; buttonContainer.style.cssText = styles.container; if (state.position) { buttonContainer.style.left = state.position.left; buttonContainer.style.top = state.position.top; } else { buttonContainer.style.bottom = '30px'; buttonContainer.style.right = '30px'; } toggleButton = document.createElement('button'); toggleButton.id = 'floating-toggle-button'; toggleButton.style.cssText = styles.button; statusIndicator = document.createElement('div'); statusIndicator.id = 'floating-status'; statusIndicator.style.cssText = styles.indicator; updateButtonState(); buttonContainer.appendChild(statusIndicator); buttonContainer.appendChild(toggleButton); document.body.appendChild(buttonContainer); bindButtonEvents(); } function bindButtonEvents() { toggleButton.addEventListener('mousedown', (e) => { if (e.button !== 0) return; state.clickStartTime = Date.now(); state.clickStartPos = { x: e.clientX, y: e.clientY }; state.isDragging = false; state.dragStart = { x: e.clientX, y: e.clientY }; state.dragOffset = { x: e.clientX - buttonContainer.getBoundingClientRect().left, y: e.clientY - buttonContainer.getBoundingClientRect().top }; buttonContainer.style.transition = 'none'; e.preventDefault(); const handleMouseMove = (e) => { if (!state.isDragging) { const dx = Math.abs(e.clientX - state.dragStart.x); const dy = Math.abs(e.clientY - state.dragStart.y); if (dx > CONFIG.DRAG_THRESHOLD || dy > CONFIG.DRAG_THRESHOLD) { state.isDragging = true; showStatus('拖动中...'); } } if (state.isDragging) { const x = e.clientX - state.dragOffset.x; const y = e.clientY - state.dragOffset.y; const maxX = window.innerWidth - buttonContainer.offsetWidth; const maxY = window.innerHeight - buttonContainer.offsetHeight; buttonContainer.style.left = `${Math.max(0, Math.min(x, maxX))}px`; buttonContainer.style.top = `${Math.max(0, Math.min(y, maxY))}px`; buttonContainer.style.right = 'auto'; buttonContainer.style.bottom = 'auto'; } }; const handleMouseUp = (e) => { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); const clickDuration = Date.now() - state.clickStartTime; const moveDistance = Math.sqrt( Math.pow(e.clientX - state.clickStartPos.x, 2) + Math.pow(e.clientY - state.clickStartPos.y, 2) ); if (state.isDragging || clickDuration > CONFIG.CLICK_TIMEOUT || moveDistance > CONFIG.DRAG_THRESHOLD) { state.isDragging = false; buttonContainer.style.transition = 'transform 0.3s ease'; saveButtonPosition(); showStatus('位置已保存'); } else { handleButtonClick(); } }; document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); }); function handleButtonClick() { state.enabled = !state.enabled; saveState(); updateButtonState(); if (state.enabled) { enableScriptFeatures(); showStatus('内容复制限制已解除'); showRefreshNotification('✅ 请刷新页面后生效'); } else { disableScriptFeatures(); showStatus('内容复制限制已恢复'); showRefreshNotification('❌ 请刷新页面后生效'); } } toggleButton.addEventListener('mouseenter', () => { if (!state.isDragging) { statusIndicator.style.opacity = '1'; statusIndicator.style.transform = 'translateY(0)'; } }); toggleButton.addEventListener('mouseleave', () => { if (!state.isDragging) { setTimeout(() => { statusIndicator.style.opacity = '0'; statusIndicator.style.transform = 'translateY(10px)'; }, 1000); } }); } function enableScriptFeatures() { EventTarget.prototype.addEventListener = function(type, listener, options) { if (!eventListeners.has(this)) { eventListeners.set(this, []); } eventListeners.get(this).push({ type, listener, options }); return originalEventMethods.addEventListener.call(this, type, listener, options); }; EventTarget.prototype.removeEventListener = function(type, listener, options) { if (eventListeners.has(this)) { const listeners = eventListeners.get(this); const index = listeners.findIndex(l => l.type === type && l.listener === listener && (l.options === options || (l.options && options && l.options.capture === options.capture)) ); if (index !== -1) { listeners.splice(index, 1); } } return originalEventMethods.removeEventListener.call(this, type, listener, options); }; removeAllEventListeners(); removeGlobalEventHandlers(); removeAntiCopyCSS(); enableInteractions(); startObservingDOM(); } function disableScriptFeatures() { EventTarget.prototype.addEventListener = originalEventMethods.addEventListener; EventTarget.prototype.removeEventListener = originalEventMethods.removeEventListener; removeAddedStyles(); stopObservingDOM(); } function removeAllEventListeners() { try { const walk = document.createTreeWalker( document.body, NodeFilter.SHOW_ELEMENT, null, false ); removeNodeEventListeners(document.body); while (walk.nextNode()) { removeNodeEventListeners(walk.currentNode); } } catch (error) { } } function removeNodeEventListeners(node) { if (eventListeners.has(node)) { const listeners = [...eventListeners.get(node)]; listeners.forEach(listener => { originalEventMethods.removeEventListener.call( node, listener.type, listener.listener, listener.options ); }); eventListeners.delete(node); } } function removeGlobalEventHandlers() { try { const targets = [document, document.body, window]; const handlers = ['oncopy', 'oncut', 'onpaste', 'onselectstart', 'oncontextmenu']; targets.forEach(target => { handlers.forEach(handler => { target[handler] = null; }); }); } catch (error) { } } let antiCopyStyle = null; function removeAntiCopyCSS() { try { antiCopyStyle = document.createElement('style'); antiCopyStyle.textContent = ` * { user-select: text !important; -webkit-user-select: text !important; -moz-user-select: text !important; -ms-user-select: text !important; pointer-events: auto !important; } [oncopy], [oncut], [onpaste], [onselectstart], [oncontextmenu] { oncopy="return true;" oncut="return true;" onpaste="return true;" onselectstart="return true;" oncontextmenu="return true;" } `; document.head.appendChild(antiCopyStyle); } catch (error) { } } function removeAddedStyles() { if (antiCopyStyle && antiCopyStyle.parentNode) { antiCopyStyle.parentNode.removeChild(antiCopyStyle); antiCopyStyle = null; } } function enableInteractions() { try { const events = ['contextmenu', 'copy', 'cut', 'paste', 'selectstart']; events.forEach(event => { window.addEventListener(event, function(e) { e.stopPropagation(); }, true); }); } catch (error) { } } let mutationObserver = null; function startObservingDOM() { if (mutationObserver) { mutationObserver.disconnect(); } mutationObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.addedNodes && mutation.addedNodes.length > 0) { for (let i = 0; i < mutation.addedNodes.length; i++) { const node = mutation.addedNodes[i]; if (node.nodeType === 1) { removeNodeEventListeners(node); } } } }); }); mutationObserver.observe(document.body, { childList: true, subtree: true }); } function stopObservingDOM() { if (mutationObserver) { mutationObserver.disconnect(); mutationObserver = null; } } // 初始化 function init() { createFloatingButton(); if (state.enabled) { enableScriptFeatures(); } else { disableScriptFeatures(); } } init(); })();