A Universal Script to Re-Enable the Selection and Copying

Enables select, right-click, copy and drag on pages that disable them. Enhanced Feature: Alt Key HyperLink Text Selection

Per 19-06-2021. Zie de nieuwste versie.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==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.7.2
// @description  Enables select, right-click, copy and drag on pages that disable them. Enhanced Feature: Alt Key HyperLink Text Selection
// @description:zh-TW 解除禁止復制、剪切、選擇文本、右鍵菜單的限制。破解鎖右鍵、文字複製、文字選取。增強功能:Alt鍵超連結文字選取。
// @include      /^https?\:\/\//
// @grant        none
// @run-at       document-start
// @namespace https://greatest.deepsurf.us/users/371179
// ==/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) {
                alert.__isDisabled__() ? console.log("alert msg disabled: ", msg) : _alert.apply(this, arguments);
            };
            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: () => {
            if (document.documentElement.hasAttribute($.utLpSelection)) return;
            $.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 3:
                        if((evt.target.textContent||"").trim().length===0)return false; //exclude elements like video
                        return true;
                    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 clickBlockingTo = 0;
                    _mAlert.__isDisabled__ = () => clickBlockingTo > +new Date;
                    $.mAlert_DOWN = () => (clickBlockingTo = +new Date + 50);
                    $.mAlert_UP = () => (clickBlockingTo = +new Date + 20);
                    window.alert = _mAlert
                }
            }

        },

        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 && evt.button === 0 && $.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);
                if ($.onceCssHighlightSelection) window.requestAnimationFrame($.onceCssHighlightSelection);
            }
        },

        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: () => {
            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;
            }
            *:hover>img[src]{pointer-events:auto !important;}

            [${$.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) => {
            document.addEventListener("mousedown", listenerPress, true); // Capture Event; (desktop)
            document.addEventListener("contextmenu", listenerPress, true); // Capture Event; (desktop&mobile)
            document.addEventListener("mouseup", listenerRelease, false); // Bubble Event;
        }

    }

    $.mainEnableScript();

    if (isSupportAdvancedEventListener()) $.lpEnable(); // top capture event for alt-click

    $.mainEvents(
        function(evt) {
            if ($.onceCssHighlightSelection) window.requestAnimationFrame($.onceCssHighlightSelection);
            if (evt.button == 2 || evt.type == "contextmenu") $.mAlert_DOWN();
        },
        function(evt) {
            if (evt.button == 2) $.mAlert_UP();
        }
    );

    console.log('userscript running - To Re-Enable Selection & Copying');

})();