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

À partir de 2021-06-22. Voir la dernière version.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==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.8.14
// @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 $nil() {}

    function isSupportAdvancedEventListener() {
        if ('_b1750' in $) return $._b1750
        var prop = 0;
        document.createAttribute('z').addEventListener('nil', $nil, {
            get passive() {
                prop++;
            },
            get once() {
                prop++;
            }
        });
        return ($._b1750 = prop == 2);
    }

    function isSupportPassiveEventListener() {
        if ('_b1650' in $) return $._b1650
        var prop = 0;
        document.createAttribute('z').addEventListener('nil', $nil, {
            get passive() {
                prop++;
            }
        });
        return ($._b1650 = prop == 1);
    }

    var getSelection = window.getSelection || Error()(),
        requestAnimationFrame = window.requestAnimationFrame || Error()(),
        getComputedStyle = window.getComputedStyle || Error()();

    const $ = {
        utSelectionColorHack: 'msmtwejkzrqa',
        utTapHighlight: 'xfcklblvkjsj',
        utLpSelection: 'gykqyzwufxpz',
        utHoverBlock: 'meefgeibrtqx', //scc_emptyblock
        ksFuncReplacerNonFalse: '___dqzadwpujtct___',
        ksEventReturnValue: ' ___ndjfujndrlsx___',
        ksSetData: '___rgqclrdllmhr___',

        eh_capture_passive: () => isSupportPassiveEventListener() ? ($._eh_capture_passive = ($._eh_capture_passive || {
            capture: true,
            passive: true
        })) : true,

        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;
            var pName = 'on' + evt.type;
            evt = null;
            Promise.resolve().then(() => {
                while (elmNode && elmNode.nodeType > 0) { //i.e. HTMLDocument or HTMLElement
                    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
            Promise.resolve().then(() => {
                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];
                return elm
            }).then(elm => {
                var selectionStyle = getComputedStyle(elm, ':selection');
                if (/^rgba\(\d+,\s*\d+,\s*\d+,\s*0\)$/.test(selectionStyle.getPropertyValue('background-color'))) document.documentElement.setAttribute($.utSelectionColorHack, "");
                return elm;
            }).then(elm => {
                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 6:
                        if ($.enableDragging) return false;
                        if (evt.target.hasAttribute('draggable')) {
                            $.enableDragging = true;
                            return false;
                        }
                        //if(evt.target.hasAttribute('draggable')&&evt.target!=window.getSelection().anchorNode)return false;
                        return true;
                    case 3:
                        if (evt.target instanceof Element && (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.call(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 (newValue === false && !isDeactivePreventDefault(this)) 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 = `
            html, html *,
            html *::before, html *::after,
            html *:hover, html *:link, html *:visited, html *:active,
            html *[style], html *[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;}

             [${$.utHoverBlock}="2"]{pointer-events:none !important;user-select:none !important;}
             img[${$.utHoverBlock}="4"]{
             display:none !important;
             }
[${$.utHoverBlock}="7"]{
padding:0 !important;
}
             [${$.utHoverBlock}="7"]>img[${$.utHoverBlock}="4"]:first-child{
             display:inline-block !important;
             opacity: 0 !important;
    padding: 0 !important;
    margin: 0 !important;
    position: relative !important;
    z-index:1 !important;
    width: 100% !important;
    height: 100% !important;
    left: 0 !important;
    top: 0 !important;
    outline: 0 !important;
    border: 0 !important;
    box-sizing: border-box !important;
    transform: initial !important;
    float: left !important;
            pointer-events:auto !important; user-select:none !important;cursor:inherit !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;
        },

        disableHoverBlock: () => {

            var nMap = new WeakMap()

            function elmParam(elm) {

                var mElm = nMap.get(elm);
                if (!mElm) nMap.set(elm, mElm = {});
                return mElm;
            }

            function overlapArea(rect1, rect2) {

                let l1 = {
                    x: rect1.left,
                    y: rect1.top
                }

                let r1 = {
                    x: rect1.right,
                    y: rect1.bottom
                }
                let l2 = {
                    x: rect2.left,
                    y: rect2.top
                }

                let r2 = {
                    x: rect2.right,
                    y: rect2.bottom
                }

                // Area of 1st Rectangle
                let area1 = Math.abs(l1.x - r1.x) * Math.abs(l1.y - r1.y);

                // Area of 2nd Rectangle
                let area2 = Math.abs(l2.x - r2.x) * Math.abs(l2.y - r2.y);

                // Length of intersecting part i.e
                // start from max(l1.x, l2.x) of
                // x-coordinate and end at min(r1.x,
                // r2.x) x-coordinate by subtracting
                // start from end we get required
                // lengths
                let x_dist = Math.min(r1.x, r2.x) - Math.max(l1.x, l2.x);
                let y_dist = (Math.min(r1.y, r2.y) - Math.max(l1.y, l2.y));
                let areaI = 0;
                if (x_dist > 0 && y_dist > 0) {
                    areaI = x_dist * y_dist;
                }

                return {
                    area1,
                    area2,
                    areaI
                };


            }

            function redirectEvent(event, toElement) {

                toElement.dispatchEvent(new event.constructor(event.type, event));
                if (event.type != 'wheel') event.preventDefault();
                event.stopPropagation();
            }

            const floatingBlockHover = new WeakMap();

            let _nImgs = [];

            function nImgFunc() {

                for (const s of _nImgs) {
                    if (s.lastTime + 800 < +new Date) {
                        s.lastTime = +new Date;
                        return s.elm
                    }
                }

                let nImg = document.createElement('img');
                nImg.onerror = function() {
                    if (this.parentNode != null) this.parentNode.removeChild(this)
                }
                nImg.setAttribute($.utHoverBlock, '4');
                const handle = function(event) {
                    if (this === event.target) {
                        if (event.button != 2) redirectEvent(event, this.parentNode)
                        Promise.resolve().then(() => {
                            for (const s of _nImgs) {
                                if (s.elm === this) {
                                    s.lastTime = +new Date
                                }
                            }
                        })
                    }
                }
                nImg.addEventListener('click', handle, true);
                nImg.addEventListener('mousedown', handle, true);
                nImg.addEventListener('mouseup', handle, true);
                nImg.addEventListener('mousemove', handle, true);
                nImg.addEventListener('mouseover', handle, true);
                nImg.addEventListener('mouseout', handle, true);
                nImg.addEventListener('mouseenter', handle, true);
                nImg.addEventListener('mouseleave', handle, true);
                //nImg.addEventListener('wheel', handle, $.eh_capture_passive());
                _nImgs.push({
                    elm: nImg,
                    lastTime: +new Date
                })

                return nImg;

            }

            const wmHoverUrl=new WeakMap();

            document.addEventListener('mouseenter', function(evt) {

                Promise.resolve()
                    .then(() => {

                        if (evt && evt.target && evt.target.parentNode) {} else {
                            return;
                        }



                    if(floatingBlockHover.get(evt.target)){

                        let url = null
                        if(evt.target.getAttribute($.utHoverBlock)=='7' && (url=wmHoverUrl.get(evt.target)) && evt.target.querySelector(`[${$.utHoverBlock}]`)==null ){

                            let _nImg = nImgFunc();

                            if (_nImg.parentNode !== evt.target) {
                                _nImg.setAttribute('src', url);
                                evt.target.insertBefore(_nImg, evt.target.firstChild);
                            }


                        }



                        return;
                    }


                        function setNULL() {
                            floatingBlockHover.set(evt.target, 1)
                        }

                        setNULL();

                        return 1;

                    }).then((ayRes) => {

                        if (!ayRes) return;

                        if (evt.target.nodeType != 1) return;
                        if ("|SVG|IMG|HTML|BODY|VIDEO|AUDIO|BR|HEAD|NOSCRIPT|SCRIPT|STYLE|TEXTAREA|AREA|INPUT|FORM|BUTTON|".indexOf(`|${evt.target.nodeName}|`) >= 0) return;



                        const targetArea = evt.target.clientWidth * evt.target.clientHeight

                        if (targetArea > 0) {} else {
                            return;
                        }

                        const targetCSS = getComputedStyle(evt.target)
                        const targetBgImage = targetCSS.getPropertyValue('background-image');
                        let exec1 = null

                        if (targetBgImage != 'none' && (exec1 = /^\s*url\s*\("?([^"\)]+\b(\.gif|\.png|\.jpeg|\.jpg|\.webp)\b[^"\)]*)"?\)\s*$/i.exec(targetBgImage))) {
                            if ((evt.target.textContent || "").trim().length > 0) return;
                            const url = exec1[1];
                            return url

                            // console.log(targetBgImage,[...exec1])
                        }



                        if (targetCSS.getPropertyValue('position') == 'absolute' && +targetCSS.getPropertyValue('z-index') > 0) {} else {
                            return;
                        }
                        if ((evt.target.textContent || "").trim().length > 0) return;

                        let possibleResults = [];

                        for (const imgElm of document.querySelectorAll('img[src]')) {
                            const param = elmParam(imgElm)
                            if (!param.area) {
                                const area = imgElm.clientWidth * imgElm.clientHeight
                                if (area > 0) param.area = area;
                            }
                            if (param.area > 0) {
                                if (targetArea > param.area * 0.9) possibleResults.push(imgElm)
                            }
                        }

                        let i = 0;
                        let j = 0;
                        for (const imgElm of possibleResults) {

                            const cmpVal = evt.target.compareDocumentPosition(imgElm)

                            /*


1: The two nodes do not belong to the same document.
2: p1 is positioned after p2.
4: p1 is positioned before p2.
8: p1 is positioned inside p2.
16: p2 is positioned inside p1.
32: The two nodes has no relationship, or they are two attributes on the same element.

            */

                            if (cmpVal & 8 || cmpVal & 16) return;
                            if (cmpVal & 2) j++; // I<p
                            else if (cmpVal & 4) break; // I>p


                            i++;

                        }

                        // before: j-1  after: j

                        let indexBefore = j - 1;
                        let indexAfter = j;
                        if (indexBefore < 0) indexBefore = 0;
                        if (indexAfter > possibleResults.length - 1) indexAfter = possibleResults.length - 1;

                        //    setTimeout(function(){
                        for (let i = indexBefore; i <= indexAfter; i++) {
                            const s = possibleResults[i];
                            const {
                                area1,
                                area2,
                                areaI
                            } = overlapArea(evt.target.getBoundingClientRect(), s.getBoundingClientRect())
                            const criteria = area1 * 0.7
                            if (areaI > 0.9 * area2) {


                                return s.getAttribute('src')


                            }
                        }
                        //   },1000);

                    }).then((sUrl) => {

                        if (typeof sUrl != 'string') return;


                        // console.log(313, evt.target, s)
                        let _nImg = nImgFunc();


                        if (_nImg.parentNode !== evt.target) {
                            _nImg.setAttribute('src', sUrl);
                            evt.target.insertBefore(_nImg, evt.target.firstChild);
                            wmHoverUrl.set(evt.target, sUrl);
                            evt.target.setAttribute($.utHoverBlock, '7');
                        }



                    })

            }, $.eh_capture_passive())

        }

    }

    document.addEventListener('selectstart', function(e) {
        if(!e||!e.target)return;
        if(e.target.nodeType>0){}else{return;}
        switch(e.target.nodeName){
            case 'BUTTON':
            case 'INPUT':
            case 'TEXTAREA':
            case 'FORM':
            case 'BODY':
            case 'HTML':
            case 'HEAD':
            case 'SCRIPT':
            case 'IMG':
            case 'A':
            case 'BR':
            case 'I':
            case 'STRONG':
                return;
        }
        if ($.enableDragging || (e.target.textContent||"").trim().length===0) {
            $.bypass = true;
            e.preventDefault();
            $.bypass = false;
        }
    }, true)

    $.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();
            if ($.enableDragging) {
                $.enableDragging = false;
            }
        }
    );

    $.disableHoverBlock();

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




})();