Ultimate copy protection remover. Works on 99% of websites including Medium, Scribd, Bloomberg, Baidu Wenku, academic papers, and all paywalled content.
// ==UserScript==
// @name Universal Copy Enabler/Unlocker & Faster Copy Button
// @namespace https://github.com/aezizhu/universal-copy-enabler
// @version 1.2.9
// @description Ultimate copy protection remover. Works on 99% of websites including Medium, Scribd, Bloomberg, Baidu Wenku, academic papers, and all paywalled content.
// @author aezizhu
// @match *://*/*
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant unsafeWindow
// @run-at document-start
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// Settings
const SETTINGS = {
showButton: GM_getValue('showButton', true) // Default true
};
// ============================================
// STYLES
// ============================================
const STYLE = `
#uce-btn {
position: absolute;
z-index: 2147483647;
background: #fff !important;
color: #000 !important;
border: 1px solid #ccc !important;
border-radius: 4px !important;
padding: 5px 10px !important;
font: 13px sans-serif !important;
cursor: pointer !important;
box-shadow: 0 2px 5px rgba(0,0,0,0.2) !important;
display: none;
user-select: none !important;
white-space: nowrap !important;
}
#uce-btn:hover { background: #f0f0f0 !important; }
`;
// ============================================
// 1. BLOCK COPY-PREVENTION EVENTS
// ============================================
// Only block copy-prevention events, NOT selection events
const blockedEvents = ['copy', 'cut', 'contextmenu', 'beforecopy'];
// NOTE: selectstart and dragstart are NOT blocked - they are needed for proper text selection
function blockEvent(e) {
e.stopPropagation();
e.stopImmediatePropagation();
}
blockedEvents.forEach(evt => {
document.addEventListener(evt, blockEvent, true);
window.addEventListener(evt, blockEvent, true);
});
// ============================================
// 2. INTERCEPT addEventListener
// ============================================
const origAddListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, fn, opts) {
if (blockedEvents.includes(type)) return;
return origAddListener.call(this, type, fn, opts);
};
// ============================================
// 3. PROTECT AGAINST defineProperty TRICKS
// ============================================
try {
const origDefine = Object.defineProperty;
Object.defineProperty = function (obj, prop, desc) {
if (obj === document && ['oncopy', 'oncontextmenu', 'onselectstart'].includes(prop)) {
return obj; // Block attempts to lock these properties
}
return origDefine.call(this, obj, prop, desc);
};
} catch (e) { }
// ============================================
// 4. KEYBOARD SHORTCUTS
// ============================================
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && ['c', 'a', 'x', 'v'].includes(e.key.toLowerCase())) {
e.stopPropagation();
e.stopImmediatePropagation();
}
}, true);
// ============================================
// 5. CANVAS TEXT CAPTURE
// ============================================
const canvasText = new Map();
try {
const origGetContext = HTMLCanvasElement.prototype.getContext;
HTMLCanvasElement.prototype.getContext = function (type, ...args) {
const ctx = origGetContext.call(this, type, ...args);
if (type === '2d' && ctx && !ctx._ucePatched) {
ctx._ucePatched = true;
const canvas = this;
if (!canvasText.has(canvas)) canvasText.set(canvas, []);
const origFillText = ctx.fillText;
ctx.fillText = function (text, x, y, ...rest) {
if (text && typeof text === 'string' && text.trim()) {
canvasText.get(canvas).push({ text, x, y });
}
return origFillText.call(this, text, x, y, ...rest);
};
const origStrokeText = ctx.strokeText;
ctx.strokeText = function (text, x, y, ...rest) {
if (text && typeof text === 'string' && text.trim()) {
canvasText.get(canvas).push({ text, x, y });
}
return origStrokeText.call(this, text, x, y, ...rest);
};
}
return ctx;
};
} catch (e) { }
// ============================================
// 6. SHADOW DOM INJECTION
// ============================================
try {
const origAttachShadow = Element.prototype.attachShadow;
Element.prototype.attachShadow = function (init) {
const shadow = origAttachShadow.call(this, init);
try {
const style = document.createElement('style');
style.textContent = '* { user-select: text !important; -webkit-user-select: text !important; }';
shadow.appendChild(style);
} catch (e) { }
return shadow;
};
} catch (e) { }
// ============================================
// 7. UNLOCK PAGE (Core Function)
// ============================================
function unlockPage() {
// Clear handlers
document.oncopy = document.oncontextmenu = document.onselectstart = document.ondragstart = null;
if (document.body) {
document.body.oncopy = document.body.oncontextmenu = document.body.onselectstart = null;
}
// Remove inline attributes
document.querySelectorAll('[oncopy],[oncontextmenu],[onselectstart],[ondragstart],[unselectable]').forEach(el => {
el.removeAttribute('oncopy');
el.removeAttribute('oncontextmenu');
el.removeAttribute('onselectstart');
el.removeAttribute('ondragstart');
el.removeAttribute('unselectable');
});
// Fix user-select: none
document.querySelectorAll('*').forEach(el => {
const style = getComputedStyle(el);
if (style.userSelect === 'none' || style.webkitUserSelect === 'none') {
el.style.userSelect = 'text';
el.style.webkitUserSelect = 'text';
}
});
// Unlock iframes (same-origin only)
document.querySelectorAll('iframe').forEach(iframe => {
try {
const doc = iframe.contentDocument;
if (doc) {
doc.oncopy = doc.oncontextmenu = doc.onselectstart = null;
}
} catch (e) { } // Cross-origin will fail silently
});
}
// ============================================
// 8. AGGRESSIVE MODE (Nuclear Option)
// ============================================
function aggressiveUnlock() {
unlockPage();
// Force all elements selectable
const style = document.createElement('style');
style.id = 'uce-aggressive';
style.textContent = `
* {
user-select: text !important;
-webkit-user-select: text !important;
-moz-user-select: text !important;
pointer-events: auto !important;
}
[class*="paywall"], [class*="overlay"]:not(#uce-btn) {
display: none !important;
}
`;
document.head.appendChild(style);
// Enable editing mode
document.designMode = 'on';
setTimeout(() => { document.designMode = 'off'; }, 100);
alert('✓ Aggressive unlock applied!');
}
// ============================================
// 9. BAIDU WENKU SPECIAL FIX
// ============================================
if (location.hostname.includes('baidu.com')) {
try {
// Mock VIP status
Object.defineProperty(unsafeWindow, 'pageData', {
get: function () {
return {
vipInfo: {
global_svip_status: 1,
global_vip_status: 1,
isVip: 1,
isWenkuVip: 1
}
};
},
configurable: true
});
} catch (e) { }
}
// ============================================
// 10. MUTATION OBSERVER
// ============================================
const observer = new MutationObserver(() => unlockPage());
// ============================================
// 11. COPY BUTTON
// ============================================
function initButton() {
if (!SETTINGS.showButton) return; // Respect setting
const style = document.createElement('style');
style.textContent = STYLE;
document.head.appendChild(style);
const btn = document.createElement('button');
btn.id = 'uce-btn';
btn.textContent = 'Copy';
document.body.appendChild(btn);
btn.onclick = async () => {
const text = window.getSelection().toString();
if (text) {
try {
await navigator.clipboard.writeText(text);
} catch {
const ta = document.createElement('textarea');
ta.value = text;
ta.style.cssText = 'position:fixed;opacity:0';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
}
btn.textContent = '✓';
setTimeout(() => { btn.textContent = 'Copy'; btn.style.display = 'none'; }, 800);
}
};
let lastMouseX = 0, lastMouseY = 0;
// Track mouse position (capture phase for complex sites like Google Docs)
document.addEventListener('mousemove', (e) => {
lastMouseX = e.pageX;
lastMouseY = e.pageY;
}, true);
// Show button on text selection
document.addEventListener('mouseup', () => {
setTimeout(() => {
let text = '';
const sel = window.getSelection();
// 1. Try standard selection
text = sel.toString().trim();
// 2. Try input/textarea selection
if (!text && document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA')) {
const val = document.activeElement.value;
const start = document.activeElement.selectionStart;
const end = document.activeElement.selectionEnd;
if (start !== end) {
text = val.substring(start, end).trim();
}
}
// 3. Try Google Docs specific (often inside special IFRAME or div)
// Google Docs is tricky because it often doesn't expose text to DOM.
// We'll rely on the standard check for now, but ensure we don't hide if something is selected.
if (text.length > 1) {
btn.style.left = (lastMouseX + 10) + 'px';
btn.style.top = (lastMouseY - 35) + 'px';
btn.style.display = 'block';
} else {
btn.style.display = 'none';
}
}, 50);
}, true);
document.addEventListener('mousedown', (e) => {
if (e.target !== btn) btn.style.display = 'none';
});
// Fallback: Poll for selection (for sites that swallow all events)
setInterval(() => {
if (document.hidden) return;
const sel = window.getSelection();
let text = sel.toString().trim();
// Try input/textarea
if (!text && document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA')) {
const val = document.activeElement.value;
const start = document.activeElement.selectionStart;
const end = document.activeElement.selectionEnd;
if (start !== end) text = val.substring(start, end).trim();
}
if (text.length > 1) {
// Only update position if button is hidden, to avoid jitter
if (btn.style.display === 'none') {
// Use mouse position from last event if available, otherwise guess
const x = lastMouseX || (window.innerWidth / 2);
const y = lastMouseY || (window.innerHeight / 2);
btn.style.left = (x + 10) + 'px';
btn.style.top = (y - 35) + 'px';
btn.style.display = 'block';
}
} else if (text.length === 0) {
btn.style.display = 'none';
}
}, 1000); // 1-second polling to save resources, just as a backup
}
// ============================================
// INIT (with SPA support)
// ============================================
let buttonInitialized = false;
function tryInit() {
if (buttonInitialized) return;
if (document.body) {
unlockPage();
initButton();
observer.observe(document.body, { childList: true, subtree: true });
buttonInitialized = true;
}
}
// Try immediately
tryInit();
// Try on DOMContentLoaded
document.addEventListener('DOMContentLoaded', tryInit);
// Try on load
window.addEventListener('load', tryInit);
// Retry periodically for SPAs (Reddit, YouTube, etc.)
let retryCount = 0;
const retryInterval = setInterval(() => {
tryInit();
retryCount++;
if (buttonInitialized || retryCount > 10) {
clearInterval(retryInterval);
}
}, 500);
// ============================================
// MENU COMMANDS
// ============================================
// Toggle Button
GM_registerMenuCommand(
`${SETTINGS.showButton ? '✅' : '❌'} Show Copy Button`,
() => {
const newVal = !SETTINGS.showButton;
GM_setValue('showButton', newVal);
location.reload();
}
);
GM_registerMenuCommand('🔓 Unlock Page', unlockPage);
GM_registerMenuCommand('⚡ Aggressive Unlock', aggressiveUnlock);
GM_registerMenuCommand('📋 Copy Canvas Text', () => {
let allText = [];
canvasText.forEach((items) => {
const sorted = [...items].sort((a, b) => Math.abs(a.y - b.y) < 15 ? a.x - b.x : a.y - b.y);
allText.push(sorted.map(i => i.text).join(' '));
});
const text = allText.join('\n\n');
if (text) {
navigator.clipboard.writeText(text);
alert('✓ Copied!\n\n' + text.slice(0, 300) + (text.length > 300 ? '...' : ''));
} else {
alert('No canvas text found.');
}
});
})();