A Universal Script to Re-Enable the Selection and Copying

解除禁止復制、剪切、選擇文本、右鍵菜單的限制。

目前為 2021-06-12 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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.5.2
// @description  Enables select, right-click, copy and drag on pages that disable them.
// @description:zh-TW 解除禁止復制、剪切、選擇文本、右鍵菜單的限制。
// @include      /^https?\:\/\//
// @grant        none
// @run-at       document-start
// @namespace https://greasyfork.org/users/371179
// ==/UserScript==
'use strict';
(function($) {

    // console.log('script at', location+"")
    if (document == null || !document.documentElement) return;

    var mKey = 'dqzadwpujtct';
    var _ksNonFalseFunc = '___nff_' + mKey + '___',
        _ksReturnValue = '___returnValue_' + mKey + '___';

    $ = {
        utSelectionColorHack: 'msmtwejkzrqa',

        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 = (window.getSelection || function() {})();
            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[_ksNonFalseFunc] = 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[_ksNonFalseFunc] !== true) {
                    var nf = $.createFuncReplacer(f, pName);
                    nf[_ksNonFalseFunc] = 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 bgColor = window.getComputedStyle(elm, ':selection').backgroundColor;
            if (/^rgba\(\d+,\s*\d+,\s*\d+,\s*0\)$/.test(bgColor)) document.documentElement.setAttribute($.utSelectionColorHack, "");
        },


        enableSelectClickCopy: function() {

            $.eyEvts = ['keydown', 'keyup', 'copy', 'contextmenu', 'select', 'selectstart', 'dragstart', 'beforecopy']; //slope: throughout


            function isDeactivePreventDefault(evt) {
                var j = $.eyEvts.indexOf(evt.type);
                return (j >= 2) || ((j == 0 || j == 1) && evt.ctrlKey && evt.keyCode == 67 && !evt.altKey && !evt.shiftKey && $.isAnySelection() === true); //j<0 return false
            }

            Event.prototype.preventDefault = (function(f) {
                return function preventDefault() {
                    if (!isDeactivePreventDefault(this)) f.apply(this);
                }
            })(Event.prototype.preventDefault);
            Event.prototype.preventDefault.toString = () => "function preventDefault() { [native code] }"

            Object.defineProperty(Event.prototype, "returnValue", {
                get() {
                    return _ksReturnValue in this ? this[_ksReturnValue] : true;
                },
                set(newValue) {
                    if (!isDeactivePreventDefault(this) && newValue === false) this.preventDefault();
                    this[_ksReturnValue] = newValue;
                },
                enumerable: true,
                configurable: true
            });

            for (var i = 2, eventsCount = $.eyEvts.length; i < eventsCount; i++) {
                document.addEventListener($.eyEvts[i], $.listenerDisableAll, {
                    capture: true, // Capture Event
                    passive: false // expected occurrence COMPLETELY before Target Capture and Target Bubble
                }, true);
            }

            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]{' +
                '-webkit-touch-callout: default !important; -webkit-user-select: auto !important; ' +
                '-khtml-user-select: auto !important; -moz-user-select: auto !important; ' +
                '-ms-user-select: auto !important; user-select: auto !important;}' +
                'html body *: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;}';

            $.enableSelectClickCopy()
            $.createCSSElement(cssStyleOnReady, document.documentElement);

        },

        mainEvents: (listenerPress, listenerRelease) => {
            (["mousedown", "click", "dblclick", "contextmenu"]).forEach(function(event) {
                document.addEventListener(event, listenerPress, {
                    capture: true, // Capture Event
                    passive: false // ensure the occurrence of 1st capture event COMPLETELY before executing other listeners
                }, true);
            });
            document.addEventListener("mouseup", listenerRelease, {
                capture: false, // Bubble Event
                passive: true // order for releasing is insignificant
            });
        }

    }

    $.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);
        });

})();