// ==UserScript== // @name A Universal Script to Re-Enable the Selection and Copying // @name:zh-TW A Universal Script to Re-Enable the Selection and Copying // @version 1.7.3 // @description Enables select, right-click, copy and drag on pages that disable them. Additional Feature: Long Press Text Selection // @description:zh-TW 解除禁止復制、剪切、選擇文本、右鍵菜單的限制。破解鎖右鍵、文字複製、文字選取。額外功能:長按文字選取。 // @include /^https?\:\/\// // @grant none // @run-at document-start // @namespace https://greasyfork.org/users/371179 // @downloadURL none // ==/UserScript== (function $$($) { 'use strict'; if (document == null || !document.documentElement) return window.requestAnimationFrame($$); // this is tampermonkey bug?? not sure //console.log('script at', location) function isSupportAdvancedEventListener() { if ('_b1750' in $) return $._b1750 var prop = 0; document.createAttribute('z').addEventListener('', null, { get passive() { prop++; }, get once() { prop++; } }); return ($._b1750 = prop == 2); } var getSelection = window.getSelection || Error()(), requestAnimationFrame = window.requestAnimationFrame || Error()(), getComputedStyle = window.getComputedStyle || Error()(); $ = { utSelectionColorHack: 'msmtwejkzrqa', utTapHighlight: 'xfcklblvkjsj', utLpSelection: 'gykqyzwufxpz', ksFuncReplacerNonFalse: '___dqzadwpujtct___', ksEventReturnValue: ' ___ndjfujndrlsx___', ksSetData: '___rgqclrdllmhr___', mAlert_DOWN: function() {}, //dummy function in case alert replacement is not valid mAlert_UP: function() {}, //dummy function in case alert replacement is not valid isAnySelection: function() { var sel = getSelection(); return !sel ? null : (typeof sel.isCollapsed == 'boolean') ? !sel.isCollapsed : (sel.toString().length > 0); }, createCSSElement: function(cssStyle, container) { var css = document.createElement('style'); //slope: DOM throughout css.type = 'text/css'; css.innerHTML = cssStyle; if (container) container.appendChild(css); return css; }, createFakeAlert: function(_alert) { if (typeof _alert != 'function') return null; function alert(msg) { setTimeout(() => (alert.__isDisabled__() ? console.log("alert msg disabled: ", msg) : _alert.apply(this, arguments)), 9); }; alert.toString = () => "function alert() { [native code] }"; return alert; }, createFuncReplacer: function(originalFunc, pName, resFX) { resFX = function(ev) { var res = originalFunc.apply(this, arguments); if (!this || this[pName] != resFX) return res; // if this is null or undefined, or this.onXXX is not this function if (res === false) return; // return undefined when "return false;" originalFunc[$.ksFuncReplacerNonFalse] = true; this[pName] = originalFunc; // restore original return res; } resFX.toString = () => originalFunc.toString(); return resFX; }, listenerDisableAll: function(evt) { var elmNode = evt.target; while (elmNode && elmNode.nodeType > 0) { //i.e. HTMLDocument or HTMLElement var pName = 'on' + evt.type var f = elmNode[pName]; if (typeof f == 'function' && f[$.ksFuncReplacerNonFalse] !== true) { var nf = $.createFuncReplacer(f, pName); nf[$.ksFuncReplacerNonFalse] = true; elmNode[pName] = nf; } elmNode = elmNode.parentNode; } }, onceCssHighlightSelection: () => { $.onceCssHighlightSelection = null var s = [...document.querySelectorAll('a,p,div,span,b,i,strong,li')].filter(elm => elm.childElementCount === 0); // randomly pick an element containing text only to avoid css style bug var elm = !s.length ? document.body : s[s.length >> 1]; var selectionStyle = getComputedStyle(elm, ':selection'); if (/^rgba\(\d+,\s*\d+,\s*\d+,\s*0\)$/.test(selectionStyle.getPropertyValue('background-color'))) document.documentElement.setAttribute($.utSelectionColorHack, ""); var elmStyle = getComputedStyle(elm) if (/^rgba\(\d+,\s*\d+,\s*\d+,\s*0\)$/.test(elmStyle.getPropertyValue('-webkit-tap-highlight-color'))) document.documentElement.setAttribute($.utTapHighlight, ""); }, isCurrentClipboardDataReplaced: function(clipboardData) { var items = clipboardData ? clipboardData.items : null; if (items && items.length > 0) { for (var i = 0, l = items.length; i < l; i++) { if (items[i].type == 'text/plain') return true; } } return false; }, replacementSetData: function(_setData, evt) { if (typeof _setData != 'function') return; function setData() { var res = _setData.apply(this, arguments); try { if (evt.clipboardData === this && this.setData === setData && evt.cancelable && evt.defaultPrevented === false) { if ($.isCurrentClipboardDataReplaced(evt.clipboardData)) { evt.preventDefault(); if (evt.defaultPrevented === true) { this.setData = _setData; delete this[$.ksSetData]; } } } } catch (e) {} return res; } setData.toString = () => _setData.toString(); evt.clipboardData.setData = setData; evt.clipboardData[$.ksSetData] = _setData; }, enableSelectClickCopy: function() { $.eyEvts = ['keydown', 'keyup', 'copy', 'contextmenu', 'select', 'selectstart', 'dragstart', 'beforecopy']; //slope: throughout function isDeactivePreventDefault(evt) { if ($.bypass) return false; var j = $.eyEvts.indexOf(evt.type); switch (j) { case -1: return false; case 0: case 1: return (evt.keyCode == 67 && (evt.ctrlKey || evt.metaKey) && !evt.altKey && !evt.shiftKey && $.isAnySelection() === true); case 2: if (!('clipboardData' in evt && 'setData' in DataTransfer.prototype)) return true; // Event oncopy not supporting clipboardData // see the richtext hack in https://www.cleancss.com/css-beautify/ // see https://developer.mozilla.org/zh-CN/docs/Web/API/Element/copy_event // see https://w3c.github.io/clipboard-apis/#widl-ClipboardEvent-clipboardData if ($.isCurrentClipboardDataReplaced(evt.clipboardData) == false) { //no replacement data if (!evt.clipboardData[$.ksSetData] && evt.cancelable && evt.defaultPrevented === false) $.replacementSetData(evt.clipboardData.setData, evt); return true; } var trimedSelectionText = getSelection().toString().trim() if (trimedSelectionText) { //there is replacement data and the selection is not empty console.log("copy event - clipboardData replacement is allowed and the selection is not empty", trimedSelectionText) return false; } else { //there is replacement data and the selection is empty return false; } break; // for js formatting only default: return true; } } Event.prototype.preventDefault = (function(f) { function preventDefault() { if (!isDeactivePreventDefault(this)) f.apply(this); } preventDefault.toString = () => f.toString(); return preventDefault; })(Event.prototype.preventDefault); Object.defineProperty(Event.prototype, "returnValue", { get() { return $.ksEventReturnValue in this ? this[$.ksEventReturnValue] : true; }, set(newValue) { if (!isDeactivePreventDefault(this) && newValue === false) this.preventDefault(); this[$.ksEventReturnValue] = newValue; }, enumerable: true, configurable: true }); for (var i = 2, eventsCount = $.eyEvts.length; i < eventsCount; i++) { document.addEventListener($.eyEvts[i], $.listenerDisableAll, true); // Capture Event; passive:false; expected occurrence COMPLETELY before Target Capture and Target Bubble } var _alert = window.alert; //slope: temporary if (typeof _alert == 'function') { var _mAlert = $.createFakeAlert(_alert); if (_mAlert) { var lastClickAt = 0; _mAlert.__isDisabled__ = () => lastClickAt + 50 > +new Date; $.mAlert_DOWN = () => (lastClickAt = +new Date); $.mAlert_UP = () => (lastClickAt = 0); window.alert = _mAlert } } }, mainEnableScript: () => { var cssStyleOnReady = ` *, body *, div, span, body *::before, body *::after, *:hover, *:link, *:visited, *:active , *[style], *[class]{ -khtml-user-select: auto !important; -moz-user-select: auto !important; -ms-user-select: auto !important; -webkit-touch-callout: default !important; -webkit-user-select: auto !important; user-select: auto !important; } a:hover, a:hover *{ -khtml-user-select: text !important; -moz-user-select: text !important;-ms-user-select: text !important; -webkit-user-select: text !important; user-select: text !important; } *:hover>img[src]{pointer-events:auto;} [${$.utSelectionColorHack}] :not(input):not(textarea)::selection{ background-color: Highlight !important; color: HighlightText !important;} [${$.utSelectionColorHack}] :not(input):not(textarea)::-moz-selection{ background-color: Highlight !important; color: HighlightText !important;} [${$.utTapHighlight}] *{ -webkit-tap-highlight-color: rgba(0, 0, 0, 0.18) !important;} html[${$.utLpSelection}] *:hover, html[${$.utLpSelection}] *:hover * { cursor:text !important;} html[${$.utLpSelection}] :not(input):not(textarea)::selection {background-color: rgba(255, 156, 179,0.5) !important;} html[${$.utLpSelection}] :not(input):not(textarea)::-moz-selection {background-color: rgba(255, 156, 179,0.5) !important;} `.trim(); $.enableSelectClickCopy() $.createCSSElement(cssStyleOnReady, document.documentElement); }, mainEvents: (listenerPress, listenerRelease) => { (["mousedown", "click", "dblclick", "contextmenu"]).forEach(function(event) { document.addEventListener(event, listenerPress, true); // Capture Event; passive:false; ensure the occurrence of 1st capture event COMPLETELY before executing other listeners }); document.addEventListener("mouseup", listenerRelease, false); // Bubble Event; passive: true/false; order for releasing is insignificant }, lpCheckPointer: function(targetElm) { if (targetElm && targetElm.nodeType == 1 && targetElm.matches('*:hover')) { if (getComputedStyle(targetElm).getPropertyValue('cursor') == 'pointer' && targetElm.textContent) return true; } return false; }, lpFullCancel: function(evt, toPreventDefault) { $.bypass = true; !toPreventDefault || evt.preventDefault() evt.stopPropagation(); evt.stopImmediatePropagation(); $.bypass = false; }, lpMouseDown: function(evt) { $.lpMouseActive = 0; if (evt.altKey && !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && $.lpCheckPointer(evt.target)) { $.lpMouseActive = 1; $.lpFullCancel(evt, false); document.documentElement.setAttribute($.utLpSelection, ''); } }, lpMouseUp: function(evt) { if ($.lpMouseActive == 1) { $.lpMouseActive = 2; document.documentElement.removeAttribute($.utLpSelection); $.lpFullCancel(evt, false); } }, lpClick: function(evt) { if ($.lpMouseActive == 2) { $.lpFullCancel(evt, false); } }, lpEnable: function() { // this is an optional feature for modern browser // the built-in browser feature has already disabled the default event behavior, the coding is just to ensure no "tailor-made behavior" occuring. document.addEventListener('mousedown', $.lpMouseDown, { capture: true, passive: true }) document.addEventListener('mouseup', $.lpMouseUp, { capture: true, passive: true }) document.addEventListener('click', $.lpClick, { capture: true, passive: true }) } } $.mainEnableScript(); $.mainEvents(function(evt) { if ($.onceCssHighlightSelection) window.requestAnimationFrame($.onceCssHighlightSelection); if (evt.type != "contextmenu" && evt.which != 3) return; if ($.cid_mouseup > 0) $.cid_mouseup = clearTimeout($.cid_mouseup); $.mAlert_DOWN(); }, function(evt) { if (evt.which != 3) return; $.cid_mouseup = setTimeout($.mAlert_UP, 17); }); console.log('userscript running - To Re-Enable Selection & Copying'); if (isSupportAdvancedEventListener()) $.lpEnable(); })();