Must Copy And Right Click
// ==UserScript==
// @name Copy And Right Click
// @name:zh-CN 右键和复制
// @version 1.1
// @description Must Copy And Right Click
// @description:zh-CN 右键和复制必须要能用
// @author 210455-aaron-twan
// @match *://*/*
// @grant none
// @license MIT
// @run-at document-start
// @namespace https://greasyfork.org/users/210455
// ==/UserScript==
(function () {
'use strict';
// 1) Inject CSS to force selectable text
const css = `
* {
-webkit-user-select: text !important;
-moz-user-select: text !important;
-ms-user-select: text !important;
user-select: text !important;
-webkit-touch-callout: default !important; /* iOS Safari */
}
`;
const style = document.createElement('style');
style.textContent = css;
// append as early as possible
const addStyle = () => {
try { document.documentElement.appendChild(style); }
catch (e) {
// if document not ready, try later
document.addEventListener('DOMContentLoaded', () => {
try { document.documentElement.appendChild(style); } catch(_) {}
}, { once: true });
}
};
addStyle();
// 2) Capture-phase handlers to block site handlers for these event types.
// We stop immediate propagation so site handlers (which often call preventDefault)
// don't run; we don't call preventDefault ourselves so the browser default copy/menu can work.
const protectedEvents = ['copy', 'cut', 'contextmenu', 'selectstart', 'mousedown', 'mouseup', 'pointerdown'];
const captureHandler = (e) => {
try {
// Allow some special cases: if the event is triggered by extension/tooling we might skip.
// But in general, stop immediate propagation to prevent site script interference.
e.stopImmediatePropagation();
// do not call preventDefault() here — we want browser default behaviors to remain possible.
} catch (err) {
// swallow
}
};
const addCaptureHandlers = (root = document) => {
protectedEvents.forEach(ev => {
root.addEventListener(ev, captureHandler, { capture: true, passive: false });
});
};
// Add as early as possible
if (document && document.documentElement) addCaptureHandlers(document);
// Also monkeypatch addEventListener to prevent site scripts from registering certain handlers that block copy/select.
// This is conservative: we only intercept attempts to add handlers of blocked event types on window/document/Element.prototype.
(function patchAddEventListener() {
const blocked = new Set(['copy','cut','contextmenu','selectstart']);
const protoList = [Window.prototype, Document.prototype, Element.prototype];
protoList.forEach(proto => {
const orig = proto.addEventListener;
if (!orig || orig.__patched_by_enable_select) return;
const newAdd = function (type, listener, options) {
try {
if (blocked.has(type)) {
// Instead of completely dropping, wrap the listener so it won't run (no-op),
// preventing site code from interfering later. Safer than throwing away user intent.
return orig.call(this, type, function noopWrapper(evt) {
// Do nothing, because we already blocked propagation in capture phase.
}, options);
}
} catch (e) {}
return orig.call(this, type, listener, options);
};
newAdd.__patched_by_enable_select = true;
proto.addEventListener = newAdd;
});
})();
// 3) Clean inline handlers and style attributes for elements currently in DOM
const cleanElement = (el) => {
try {
// remove inline handlers commonly used to block selection
['oncopy','oncut','oncontextmenu','onselectstart','onmousedown','onmouseup','onpointerdown'].forEach(name => {
if (el[name]) {
try { el[name] = null; } catch(e) {}
}
});
// enforce style
if (el.style) {
try {
el.style.userSelect = 'text';
el.style.webkitUserSelect = 'text';
el.style.MozUserSelect = 'text';
el.style.msUserSelect = 'text';
} catch(e) {}
}
// remove "unselectable" attribute used by some sites
if (el.hasAttribute && (el.hasAttribute('unselectable') || el.getAttribute('unselectable') === 'on')) {
try { el.removeAttribute('unselectable'); } catch(e) {}
}
} catch (err) { /* ignore */ }
};
const cleanAll = (root = document) => {
try {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null, false);
let n = walker.currentNode;
while (n) {
cleanElement(n);
n = walker.nextNode();
}
} catch (e) {}
};
// run when DOM is ready
const onReady = () => {
try {
cleanAll(document);
addCaptureHandlers(document);
} catch (e) {}
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', onReady, { once: true });
} else {
onReady();
}
// 4) Observe mutations (dynamic content)
const mo = new MutationObserver(muts => {
muts.forEach(m => {
if (m.type === 'childList') {
m.addedNodes.forEach(node => {
if (node.nodeType === 1) {
cleanElement(node);
// also clean subtree
try {
node.querySelectorAll && node.querySelectorAll('*').forEach(cleanElement);
} catch (e) {}
}
});
} else if (m.type === 'attributes' && m.target) {
cleanElement(m.target);
}
});
});
try {
mo.observe(document.documentElement || document, { childList: true, subtree: true, attributes: true, attributeFilter: ['oncopy','oncontextmenu','unselectable','style'] });
} catch (e) {
// if observe failed early, try again after ready
document.addEventListener('DOMContentLoaded', () => {
try { mo.observe(document.documentElement || document, { childList: true, subtree: true, attributes: true, attributeFilter: ['oncopy','oncontextmenu','unselectable','style'] }); } catch(_) {}
}, { once: true });
}
// Optional: expose a quick toggle in console for debugging
window.__enableSelect_debug = {
cleanAll: () => { try { cleanAll(document); console.info('enable-select: cleaned'); } catch(e){console.error(e);} },
stop: () => {
try {
protectedEvents.forEach(ev => document.removeEventListener(ev, captureHandler, { capture: true }));
mo.disconnect();
console.info('enable-select: stopped');
} catch (e) { console.error(e); }
}
};
// Hint: If a page uses overlays that intercept clicks, you can open devtools and inspect the element under pointer,
// then remove that overlay element manually (delete it in Elements panel) — this script will try to help but can't handle every weird custom anti-select scheme.
})();