// ==UserScript==
// @name 解除复制限制
// @namespace https://greasyfork.org/zh-CN/users/1257285-dreamprostudio
// @version 1.0
// @description 解除网页文本复制限制
// @author DreamProStudio
// @run-at document-start
// @match *
// @grant none
// @license GPL-3.0-or-later
// @homepageURL http://www.coolapk.com/u/28432077
// @supportURL https://greasyfork.org/zh-CN/users/1257285-dreamprostudio
// ==/UserScript==
(function() {
// 定义规则和黑名单列表
const rules = {
black_rule: {
name: "black",
hook_eventNames: "",
unhook_eventNames: ""
},
default_rule: {
name: "default",
hook_eventNames: "contextmenu|select|selectstart|copy|cut|dragstart",
unhook_eventNames: "mousedown|mouseup|keydown|keyup",
dom0: true,
hook_addEventListener: true,
hook_preventDefault: true,
hook_set_returnValue: true,
add_css: true
}
};
const lists = {
black_list: [
/.*\.youtube\.com.*/,
/.*\.wikipedia\.org.*/,
/mail\.qq\.com.*/,
/translate\.google\..*/,
/.*\.bing\.com.*/
]
};
// 随机生成存储名以避免冲突
const storageName = getRandStr('qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM', parseInt(Math.random() * 12 + 8));
const originalAddEventListener = EventTarget.prototype.addEventListener;
const originalPreventDefault = Event.prototype.preventDefault;
let hook_eventNames, unhook_eventNames, eventNames;
// 重写 addEventListener 方法
function addEventListener(type, listener, options) {
if (hook_eventNames.includes(type)) {
originalAddEventListener.call(this, type, returnTrue, options);
} else if (unhook_eventNames.includes(type)) {
const funcsName = `${storageName}${type}${options ? 't' : 'f'}`;
if (!this[funcsName]) {
this[funcsName] = [];
originalAddEventListener.call(this, type, options ? unhook_t : unhook_f, options);
}
this[funcsName].push(listener);
} else {
originalAddEventListener.call(this, type, listener, options);
}
}
// 清理 DOM0 事件处理程序
function clearLoop() {
const elements = [...document.querySelectorAll('*'), document];
elements.forEach(element => {
eventNames.forEach(eventName => {
const name = 'on' + eventName;
if (element[name] !== null && element[name] !== handleEvent) {
if (unhook_eventNames.includes(eventName)) {
element[storageName + name] = element[name];
element[name] = handleEvent;
} else {
element[name] = null;
}
}
});
});
}
// 处理事件的方法
function handleEvent(e) {
const name = storageName + 'on' + e.type;
if (this[name]) this[name](e);
e.returnValue = true;
return true;
}
// 解除钩子事件的方法
function unhook(e, self, funcsName) {
self[funcsName].forEach(func => func(e));
e.returnValue = true;
return true;
}
// 用于捕获捕获阶段的解除钩子事件处理程序
function unhook_t(e) {
return unhook(e, this, `${storageName}${e.type}t`);
}
// 用于冒泡阶段的解除钩子事件处理程序
function unhook_f(e) {
return unhook(e, this, `${storageName}${e.type}f`);
}
// 返回 true 的事件处理程序
function returnTrue() {
return true;
}
// 生成随机字符串
function getRandStr(chars, len) {
return Array.from({ length: len }, () => chars.charAt(Math.floor(Math.random() * chars.length))).join('');
}
// 添加自定义样式到页面
function addStyle(css) {
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
}
// 根据 URL 获取适用的规则
function getRule(url) {
return lists.black_list.some(regex => regex.test(url)) ? rules.black_rule : rules.default_rule;
}
// 初始化脚本
function init() {
const url = `${window.location.host}${window.location.pathname}`;
const rule = getRule(url);
hook_eventNames = rule.hook_eventNames.split("|");
unhook_eventNames = rule.unhook_eventNames.split("|");
eventNames = [...hook_eventNames, ...unhook_eventNames];
// DOM0 事件处理和样式添加
if (rule.dom0) {
const observer = new MutationObserver(clearLoop);
observer.observe(document, { childList: true, subtree: true });
window.addEventListener('load', clearLoop, true);
clearLoop();
}
// 重写事件监听和默认行为
if (rule.hook_addEventListener) {
EventTarget.prototype.addEventListener = addEventListener;
}
if (rule.hook_preventDefault) {
Event.prototype.preventDefault = function() {
if (!eventNames.includes(this.type)) {
originalPreventDefault.apply(this, arguments);
}
};
}
if (rule.hook_set_returnValue) {
Object.defineProperty(Event.prototype, 'returnValue', {
set: function(value) {
if (value !== true && eventNames.includes(this.type)) {
this.returnValue = true;
}
}
});
}
console.debug('url:', url, 'storageName:', storageName, 'rule:', rule.name);
// 添加自定义 CSS
if (rule.add_css) {
addStyle('html, * { -webkit-user-select: text !important; }');
}
}
// 执行初始化
init();
})();