网页限制解除(改)

通杀大部分网站,可解除禁止复制、剪切、选择文本、右键菜单的限制。本脚本由 yuanjie221 在原作者 Cat73 的基础上修改,因与搜索跳转脚本冲突进行了调整。

// ==UserScript==
// @name              网页限制解除(改)
// @namespace         https://greasyfork.org/zh-CN/scripts/35977
// @version           4.4.8
// @description       通杀大部分网站,可解除禁止复制、剪切、选择文本、右键菜单的限制。本脚本由 yuanjie221 在原作者 Cat73 的基础上修改,因与搜索跳转脚本冲突进行了调整。
// @author            Cat73 & yuanjie221(修改)
// @contributor       yuanjie221
// @match             *://*/*
// @exclude           *www.bilibili.com/video*
// @exclude           *www.bilibili.com/v*
// @exclude           *www.bilibili.com/s/*
// @exclude           *www.bilibili.com/bangumi*
// @exclude           https://www.bilibili.com/medialist/play/*
// @exclude           *www.youtube.com/watch*
// @exclude           *www.panda.tv*
// @exclude           *www.github.com*
// @exclude           https://lanhuapp.com/*
// @exclude           https://www.douyu.com/*
// @exclude           https://www.zhihu.com/signin?*
// @exclude           https://tieba.baidu.com/*
// @exclude           https://v.qq.com/*
// @exclude           *.taobao.com/*
// @exclude           *tmall.com*
// @exclude           *signin*
// @exclude           *zcool.com.cn/* // 可移除以测试站酷
// @grant             GM_getValue
// @grant             GM_setValue
// @grant             GM_addStyle
// @grant             GM_deleteValue
// @grant             GM_setClipboard
// @grant             GM_registerMenuCommand
// @run-at            document-start
// ==/UserScript==
(function() {
    'use strict';
    console.log('网页限制解除: 脚本启动于', new Date().toLocaleString());

    // 默认配置
    var settingData = {
        status: 1,
        version: 0.1,
        message: '啦啦啦,啦啦啦,我是卖报的小行家',
        positionTop: '0',
        positionLeft: '0',
        positionRight: 'auto',
        addBtn: true, // 确保显示侧边面板
        connectToTheServer: false,
        waitUpload: [],
        currentURL: 'null',
        shortcut: 3,
        rules: {
            rule_def: {
                name: 'default',
                hook_eventNames: 'contextmenu|select|selectstart|copy|cut|dragstart|mousemove|beforeunload',
                unhook_eventNames: 'mousedown|mouseup|keydown|keyup',
                dom0: true,
                hook_addEventListener: true,
                hook_preventDefault: true,
                hook_set_returnValue: true,
                add_css: true
            },
            rule_plus: {
                name: 'default',
                hook_eventNames: 'contextmenu|select|selectstart|copy|cut|dragstart|mousedown|mouseup|mousemove|beforeunload',
                unhook_eventNames: 'keydown|keyup',
                dom0: true,
                hook_addEventListener: true,
                hook_preventDefault: true,
                hook_set_returnValue: true,
                add_css: true
            },
            rule_zhihu: {
                name: 'default',
                hook_eventNames: 'contextmenu|select|selectstart|copy|cut|dragstart|mousemove',
                unhook_eventNames: 'keydown|keyup',
                dom0: true,
                hook_addEventListener: true,
                hook_preventDefault: true,
                hook_set_returnValue: true,
                add_css: true
            }
        },
        data: [
            'b.faloo.com', 'bbs.coocaa.com', 'book.hjsm.tom.com', 'book.zhulang.com',
            'book.zongheng.com', 'chokstick.com', 'chuangshi.qq.com', 'city.udn.com',
            'cutelisa55.pixnet.net', 'huayu.baidu.com', 'imac.hk', 'life.tw',
            'luxmuscles.com', 'news.missevan.com', 'read.qidian.com', 'www.15yan.com',
            'www.17k.com', 'www.18183.com', 'www.360doc.com', 'www.coco01.net',
            'www.eyu.com', 'www.hongshu.com', 'www.hongxiu.com', 'www.imooc.com',
            'www.jjwxc.net', 'www.readnovel.com', 'www.tadu.com', 'www.xxsy.net',
            'www.z3z4.com', 'www.zhihu.com', 'yuedu.163.com', 'www.ppkao.com',
            'movie.douban.com', 'www.ruiwen.com', 'vipreader.qidian.com', 'www.pigai.org',
            'www.shangc.net', 'www.myhtlmebook.com', 'www.yuque.com', 'www.longmabookcn.com',
            'www.alphapolis.co.jp', 'www.sdifen.com', 'votetw.com', 'boke112.com',
            'www.myhtebooks.com', 'www.xiegw.cn', 'www.uta-net.com', 'www.bimiacg.net',
            'www.dianyuan.com', 'origenapellido.com', '3g.163.com', 'www.lu-xu.com',
            'leetcode.cn', 'www.jianbiaoku.com', 'www.soyoung.com', 'doc.guandang.net',
            'www.51dongshi.com', 'm.haodf.com', 'www.daodoc.com', 'www.wcqjyw.com',
            'www.szxx.com.cn'
        ]
    };

    var rwl_userData = GM_getValue('rwl_userData') || settingData;
    var hostname = window.location.hostname;
    var btn_node = null;
    var rule = null;
    var list = null;
    var hasFrame = false;

    var storageName = 'iqxinStorageName';
    var hook_eventNames, unhook_eventNames, eventNames;
    var EventTarget_addEventListener = EventTarget.prototype.addEventListener;
    var document_addEventListener = document.addEventListener;
    var Event_preventDefault = Event.prototype.preventDefault;

    // 更新配置
    for (let value in settingData) {
        if (!rwl_userData.hasOwnProperty(value)) {
            rwl_userData[value] = settingData[value];
            GM_setValue('rwl_userData', rwl_userData);
        }
    }

    version_up_3_to_4();
    list = get_black_list();

    // 延迟初始化以确保 DOM 就绪
    document.addEventListener('DOMContentLoaded', function() {
        console.log('网页限制解除: DOM 就绪,启动初始化');
        if (rwl_userData.addBtn) {
            addBtn();
            btn_node = document.getElementById('black_node');
            var timer = setInterval(function() {
                console.log('网页限制解除: 检查 black_node');
                if (document.getElementById('black_node')) {
                    console.log('网页限制解除: 找到 black_node,启动 qxinStart');
                    clearInterval(timer);
                    qxinStart();
                } else {
                    console.log('网页限制解除: 未找到 black_node,重新调用 addBtn');
                    addBtn();
                }
            }, 500);
        }
    });

    GM_registerMenuCommand('复制限制解除 设置', setMenu);

    function qxinStart() {
        console.log('网页限制解除: qxinStart 启动');
        addDragEven();
        setBtnClick();
        if (check_black_list(list, hostname)) {
            try {
                if (rwl_userData.addBtn) {
                    btn_node.checked = true;
                }
            } catch (e) {
                console.error('网页限制解除: 设置 btn_node.checked 出错:', e);
            } finally {
                init();
            }
        }
    }

    function addBtn() {
        console.log('网页限制解除: addBtn 调用, addBtn:', rwl_userData.addBtn);
        if (!rwl_userData.addBtn) return;

        var node = document.createElement('remove-web-limits-iqxin');
        node.id = 'rwl-iqxin';
        node.className = 'rwl-exempt';

        var screenClientHeight = document.documentElement.clientHeight;
        var tempHeight = rwl_userData.positionTop > screenClientHeight ? screenClientHeight - 40 : rwl_userData.positionTop;
        tempHeight = tempHeight < 0 ? 0 : tempHeight;

        // 使用 GM_addStyle 避免 CSP 限制
        GM_addStyle(`
            #rwl-iqxin {
                position: fixed;
                top: ${tempHeight}px;
                left: ${rwl_userData.positionLeft}px;
                right: ${rwl_userData.positionRight};
                transform: translate(-95%, 0);
                width: 85px;
                height: 25px;
                font-size: 12px;
                font-weight: 500;
                font-family: Verdana, Arial, '宋体';
                color: #fff;
                background: #333;
                z-index: 2147483647;
                margin: 0;
                opacity: 0.05;
                transition: 0.3s;
                overflow: hidden;
                user-select: none;
                text-align: center;
                white-space: nowrap;
                line-height: 25px;
                padding: 0 16px;
                border: 1px solid #ccc;
                border-width: 1px 1px 1px 0;
                border-bottom-right-radius: 5px;
                box-sizing: content-box;
            }
            #rwl-iqxin input {
                margin: 0;
                padding: 0;
                vertical-align: middle;
                -webkit-appearance: checkbox !important;
                -moz-appearance: checkbox;
                position: static;
                clip: auto;
                opacity: 1;
                cursor: pointer;
            }
            #rwl-iqxin.rwl-active-iqxin {
                left: 0px;
                transform: translate(0, 0);
                opacity: 0.9;
                height: 32px;
                line-height: 32px;
            }
            #rwl-iqxin label {
                margin: 0;
                padding: 0;
                font-weight: 500;
            }
            #rwl-iqxin #rwl-setbtn {
                margin: 0 4px 0 0;
                padding: 0 0 0 4px;
                border: none;
                border-radius: 2px;
                cursor: pointer;
                background: #fff;
                color: #000;
            }
        `);

        window.onresize = function() {
            var screenClientHeight = document.documentElement.clientHeight;
            var tempHeight = rwl_userData.positionTop > screenClientHeight ? screenClientHeight - 40 : rwl_userData.positionTop;
            GM_addStyle(`#rwl-iqxin { top: ${tempHeight}px; }`);
        };

        node.innerHTML = '<qxinbutton type="qxinbutton" id="rwl-setbtn"> 设置 </qxinbutton> <lalala style="cursor:move; font-size:12px;">限制解除</lalala> <input type="checkbox" name="" id="black_node">';
        if (window.self === window.top) {
            if (document.body) {
                document.body.appendChild(node);
            } else if (document.documentElement) {
                document.documentElement.appendChild(node);
            } else {
                console.error('网页限制解除: 无法添加节点,body 和 documentElement 不可用');
            }
        }

        node.addEventListener('mouseover', function() {
            node.classList.add('rwl-active-iqxin');
        });
        node.addEventListener('mouseleave', function() {
            setTimeout(function() {
                node.classList.remove('rwl-active-iqxin');
                black_check(black_node.checked);
            }, 100);
        });
    }

    function setBtnClick() {
        var setBtn = document.querySelector('#rwl-setbtn');
        if (setBtn) setBtn.addEventListener('click', setMenu);
    }

    function setMenu() {
        console.log('网页限制解除: 打开设置菜单');
        var oldEditBox = document.querySelector('#rwl-setMenu');
        if (oldEditBox) {
            oldEditBox.parentNode.removeChild(oldEditBox);
            return;
        }
        var userSetting = GM_getValue('rwl_userData');
        var btnchecked = userSetting.addBtn ? 'checked' : '';

        var odom = document.createElement('div');
        odom.id = 'rwl-setMenu';
        GM_addStyle(`
            #rwl-setMenu {
                position: fixed;
                top: 100px;
                left: 50px;
                padding: 10px;
                background: #fff;
                border-radius: 4px;
                text-align: left;
                font-size: 14px;
                z-index: 999999;
                border: 1px solid cornflowerblue;
            }
            #rwl-setMenu p { margin: 5px auto; }
            #rwl-setMenuSave, #rwl-reset, #rwl-setMenuClose {
                margin: 0;
                padding: 0 2px;
                border: none;
                border-radius: 2px;
                cursor: pointer;
                background: #fff;
                color: #000;
            }
            #rwl-reset { border: 1px solid #666; }
            #rwl-setMenuSave { border: 1px solid green; }
            #rwl-setMenuClose { border: 1px solid red; }
        `);

        odom.innerHTML = `
            <p>距离顶部距离(单位 像素) <input id='positiontop' type='text' value="${userSetting.positionTop}"></p>
            <p id='rwl-shortcuts' title='快捷键'>快捷键:
                <select id='rwl-shortcut'>
                    <option value='off' ${userSetting.shortcut == 0 ? 'selected' : ''}>关闭</option>
                    <option value='f1' ${userSetting.shortcut == 1 ? 'selected' : ''}> F1 </option>
                    <option value='ctrlf1' ${userSetting.shortcut == 2 ? 'selected' : ''}>ctrl + F1</option>
                    <option value='ctrlc' ${userSetting.shortcut == 3 ? 'selected' : ''}>ctrl + C</option>
                </select>
            </p>
            <label><p>显示按钮<input id='btnchecked' type='checkbox' ${btnchecked}> 点击脚本管理扩展可再次打开设置</p></label>
            <p>问题反馈地址: <a target='_blank' href='https://github.com/qxinGitHub/Remove-web-limits-'>GitHub(yuanjie221)</a>, <a target='_blank' href='https://greasyfork.org/zh-CN/scripts/28497-remove-web-limits-modified'>GreasyFork(yuanjie221)</a></p>
            <p>原作者: Cat73。本脚本由 yuanjie221 在其基础上修改</p>
            <p>能力有限,不能每个网站都完美</p>
            <p>如需反馈,请附上详细网址</p>
            <p>数据存储为 JSON,修改时注意引号和逗号</p>
            <textarea wrap='off' cols='45' rows='20' style='overflow:auto;border-radius:4px;'>${JSON.stringify(userSetting.data, null, 4)}</textarea>
            <br>
            <qxinbutton id='rwl-reset'>清空设置</qxinbutton>  
            <qxinbutton id='rwl-setMenuSave'>保存</qxinbutton>  
            <qxinbutton id='rwl-setMenuClose' onclick='this.parentNode.parentNode.removeChild(this.parentNode);' title='无法关闭请刷新界面'>关闭</qxinbutton>  
            <span style='font-size:0.7em;'>--| yuanjie221 v4.4.8 2022-12-13 |--</span>
        `;
        document.body.appendChild(odom);

        document.querySelector('#rwl-setMenuSave').addEventListener('click', saveSetting);
        document.querySelector('#rwl-setMenuClose').addEventListener('click', closeMenu);
        document.querySelector('#rwl-reset').addEventListener('click', rwlReset);
    }

    function saveSetting() {
        var positionTop = document.querySelector('#rwl-setMenu #positiontop').value;
        var shortcut = document.querySelector('#rwl-setMenu #rwl-shortcut').selectedIndex;
        var addBtnChecked = document.querySelector('#rwl-setMenu #btnchecked').checked;
        var codevalue = document.querySelector('#rwl-setMenu textarea').value;
        if (codevalue) {
            var userSetting = GM_getValue('rwl_userData');
            userSetting.addBtn = addBtnChecked;
            userSetting.data = JSON.parse(codevalue);
            userSetting.positionTop = parseInt(positionTop);
            userSetting.shortcut = parseInt(shortcut);
            GM_setValue('rwl_userData', userSetting);
            setTimeout(function() {
                window.location.reload();
            }, 300);
        } else {
            alert('输入为空');
        }
        closeMenu();
    }

    function rwlReset() {
        GM_deleteValue('rwl_userData');
        window.location.reload();
    }

    function closeMenu() {
        var oldEditBox = document.querySelector('#rwl-setMenu');
        if (oldEditBox) oldEditBox.parentNode.removeChild(oldEditBox);
    }

    function addDragEven() {
        setTimeout(function() {
            try {
                dragBtn();
            } catch (e) {
                console.error('网页限制解除: dragBtn 出错:', e);
            }
        }, 1000);
    }

    function dragBtn() {
        var rwl_node = document.querySelector('#rwl-iqxin');
        if (!rwl_node) return;
        rwl_node.addEventListener('mousedown', function(event) {
            rwl_node.style.transition = 'none';
            var disX = event.clientX - rwl_node.offsetLeft;
            var disY = event.clientY - rwl_node.offsetTop;

            var move = function(event) {
                rwl_node.style.left = event.clientX - disX + 'px';
                rwl_node.style.top = event.clientY - disY + 'px';
            };

            document.addEventListener('mousemove', move);
            document.addEventListener('mouseup', function() {
                rwl_node.style.transition = '0.3s';
                document.removeEventListener('mousemove', move);
                rwl_node.style.right = rwl_userData.positionRight = 'auto';
                rwl_node.style.left = rwl_userData.positionLeft = 0;
                rwl_userData.positionTop = rwl_node.offsetTop;
                GM_setValue('rwl_userData', rwl_userData);
            });
        });
    }

    function init() {
        rule = clear();
        hook_eventNames = rule.hook_eventNames.split('|');
        unhook_eventNames = rule.unhook_eventNames.split('|');
        eventNames = hook_eventNames.concat(unhook_eventNames);

        if (rule.dom0) {
            setInterval(clearLoop, 10 * 1000);
            setTimeout(clearLoop, 1500);
            window.addEventListener('load', clearLoop, true);
            clearLoop();
        }

        if (rule.hook_addEventListener) {
            EventTarget.prototype.addEventListener = addEventListener;
            document.addEventListener = addEventListener;
            if (hasFrame) {
                for (let i = 0; i < hasFrame.length; i++) {
                    hasFrame[i].contentWindow.document.addEventListener = addEventListener;
                }
            }
        }

        if (rule.hook_preventDefault) {
            Event.prototype.preventDefault = function() {
                if (hook_eventNames.indexOf(this.type) < 0) {
                    Event_preventDefault.apply(this, arguments);
                }
            };
            if (hasFrame) {
                for (let i = 0; i < hasFrame.length; i++) {
                    hasFrame[i].contentWindow.Event.prototype.preventDefault = function() {
                        if (hook_eventNames.indexOf(this.type) < 0) {
                            Event_preventDefault.apply(this, arguments);
                        }
                    };
                }
            }
        }

        if (rule.hook_set_returnValue) {
            Event.prototype.__defineSetter__('returnValue', function() {
                if (this.returnValue !== true && hook_eventNames.indexOf(this.type) >= 0) {
                    this.returnValue = true;
                }
            });
        }

        if (rule.add_css) {
            GM_addStyle('html, :not([class*="rwl-exempt"]) {-webkit-user-select:text!important; -moz-user-select:text!important;} :not([class*="rwl-exempt"]) ::selection {color:#fff; background:#3390FF!important;}');
        }
    }

    function addEventListener(type, func, useCapture) {
        var _addEventListener = this === document ? document_addEventListener : EventTarget_addEventListener;
        if (hook_eventNames.indexOf(type) >= 0) {
            _addEventListener.apply(this, [type, returnTrue, useCapture]);
        } else if (unhook_eventNames.indexOf(type) >= 0) {
            var funcsName = storageName + type + (useCapture ? '_t' : '_f');
            if (this[funcsName] === undefined) {
                this[funcsName] = [];
                _addEventListener.apply(this, [type, useCapture ? unhook_t : unhook_f, useCapture]);
            }
            this[funcsName].push(func);
        } else {
            _addEventListener.apply(this, arguments);
        }
    }

    function clearLoop() {
        rule = clear();
        var elements = getElements();
        for (var i in elements) {
            for (var j in eventNames) {
                var name = 'on' + eventNames[j];
                if (Object.prototype.toString.call(elements[i]) == '[object String]') continue;
                if (elements[i][name] !== null && elements[i][name] !== onxxx) {
                    if (unhook_eventNames.indexOf(eventNames[j]) >= 0) {
                        elements[i][storageName + name] = elements[i][name];
                        elements[i][name] = onxxx;
                    } else {
                        elements[i][name] = null;
                    }
                }
            }
        }
        document.onmousedown = function() { return true; };
    }

    function returnTrue() { return true; }
    function unhook_t(e) { return unhook(e, this, storageName + e.type + '_t'); }
    function unhook_f(e) { return unhook(e, this, storageName + e.type + '_f'); }
    function unhook(e, self, funcsName) {
        var list = self[funcsName];
        for (var i in list) list[i](e);
        e.returnValue = true;
        return true;
    }
    function onxxx(e) {
        var name = storageName + 'on' + e.type;
        this[name](e);
        e.returnValue = true;
        return true;
    }

    function getElements() {
        var elements = Array.prototype.slice.call(document.getElementsByTagName('*'));
        elements.push(document);
        var frames = document.querySelectorAll('frame');
        if (frames) {
            hasFrame = frames;
            for (let i = 0; i < frames.length; i++) {
                var frames_element = Array.prototype.slice.call(frames[i].contentWindow.document.querySelectorAll('*'));
                elements.push(frames[i].contentWindow.document);
                elements = elements.concat(frames_element);
            }
        }
        return elements;
    }

    function get_black_list() {
        return rwl_userData.data.filter(item => item.length > 1);
    }

    function check_black_list(list, host) {
        for (let i = 0; i < list.length; i++) {
            if (~host.indexOf(list[i])) return i + 1;
        }
        return false;
    }

    function black_check(bool) {
        var list = GM_getValue('rwl_userData').data;
        var check = check_black_list(list, hostname);
        if (bool && !check) {
            list = list.concat(hostname);
            rwl_userData.currentURL = window.location.href;
            saveData(list);
            init();
        } else if (!bool && check) {
            list.splice(check - 1, 1);
            saveData(list);
            setTimeout(function() {
                window.location.reload(true);
                console.log('网页限制解除: 刷新页面');
            }, 350);
        }
    }

    function saveData(lists) {
        lists = lists.filter(item => item.length > 1);
        rwl_userData.data = lists.sort();
        GM_setValue('rwl_userData', rwl_userData);
        console.log('网页限制解除: 数据已本地保存,无网络上传');
    }

    function unique(arr) {
        var ret = [];
        for (var i = 0; i < arr.length; i++) {
            if (ret.indexOf(arr[i]) === -1) ret.push(arr[i]);
        }
        return ret;
    }

    function setClipboard() {
        var text = window.getSelection().toString();
        GM_setClipboard(text);
    }

    function hotkey() {
        var a = window.event.keyCode;
        if (a == 112 && rwl_userData.shortcut == 1) {
            event.preventDefault();
            setClipboard();
            event.keyCode = 0;
            event.returnValue = false;
            return false;
        } else if (a == 112 && event.ctrlKey && rwl_userData.shortcut == 2) {
            setClipboard();
        } else if (a == 67 && event.ctrlKey && rwl_userData.shortcut == 3) {
            setClipboard();
        }
    }
    document.onkeydown = hotkey;

    function clear() {
        switch (hostname) {
            case 'chuangshi.qq.com': clear_chuangshi(); break;
            case 'votetw.com': clear_votetw(); break;
            case 'www.myhtebooks.com': clear_covers('.fullimg'); break;
            case 'www.z3z4.com': clear_covers('.moviedownaddiv'); break;
            case 'huayu.baidu.com': clear_covers('#jqContextMenu'); break;
            case 'www.myhtlmebook.com': clear_covers('img.fullimg'); break;
            case 'www.szxx.com.cn': clear_covers('img#adCover'); break;
            case 'zhihu.com':
            case 'www.zhihu.com': return rwl_userData.rules.rule_zhihu;
            case 't.bilibili.com': clear_link_bilibili(); break;
            case 'www.uslsoftware.com': clear_covers('.protect_contents-overlay'); clear_covers('.protect_alert'); return rwl_userData.rules.rule_plus;
            case 'www.longmabookcn.com': clear_covers('.fullimg'); return rwl_userData.rules.rule_plus;
            case 'boke112.com': return rwl_userData.rules.rule_plus;
            case 'www.shangc.net': return rwl_userData.rules.rule_plus;
            case 'www.daodoc.com': clear_marks('.marks'); break;
            case 'www.wcqjyw.com': clear_marks('.marks'); break;
            case 'www.jianbiaoku.com': clear_marks('.layui-layer-shade'); break;
        }
        return rwl_userData.rules.rule_def;
    }

    function clear_covers(ele) {
        var odiv = document.querySelector(ele);
        if (odiv) odiv.parentNode.removeChild(odiv);
    }

    function clear_link_bilibili() {
        var odiv = document.querySelector('.description');
        if (odiv) {
            var tDiv = odiv.querySelector('.content-ellipsis');
            var aDiv = odiv.querySelector('a');
            odiv.appendChild(tDiv);
        }
    }

    function clear_votetw() {
        var odivs = document.querySelectorAll('.mw-parser-output>div');
        odivs.forEach(function(value) {
            value.setAttribute('style', '');
        });
    }

    function clear_marks(node) {
        GM_addStyle(`${node} { display: none !important; }`);
    }

    function clear_chuangshi() {
        console.log('网页限制解除: 处理 chuangshi.qq.com');
        function tounicode(data) {
            if (!data) return '请输入汉字';
            var str = '';
            for (var i = 0; i < data.length; i++) {
                str += '\\u' + parseInt(data[i].charCodeAt(0), 10).toString(16);
            }
            return str;
        }
        function tohanzi(data) {
            if (!data) return '请输入十六进制unicode';
            data = data.split('\\u');
            var str = '';
            for (var i = 0; i < data.length; i++) {
                str += String.fromCharCode(parseInt(data[i], 16));
            }
            return str;
        }
        Array.prototype.each = function(trans) {
            for (var i = 0; i < this.length; i++) {
                this[i] = trans(this[i], i, this);
            }
            return this;
        };
        Array.prototype.map = function(trans) {
            return [].concat(this).each(trans);
        };
        RegExp.escape = function(str) {
            return new String(str).replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1');
        };
        function properties(obj) {
            var props = [];
            for (var p in obj) props.push(p);
            return props;
        }
        var bookreadercontent = document.querySelector('.bookreadercontent');
        if (bookreadercontent) {
            var str = bookreadercontent.innerText;
            var strUnicode = tounicode(str);
            var replacements = {
                'e2af': '4e09', 'e2c9': '4e3b', 'e2d6': '4e48', 'e2b2': '4e4b',
                'e2a6': '4e5f', 'e294': '4e8b', 'e2e9': '4e8c', 'e30a': '4e8e',
                'e292': '4e94', 'e298': '4e9b', 'e2a2': '4ee3', 'e2f0': '4f46',
                'e30e': '4f4d', 'e305': '4f53', 'e296': '4f5c', 'e2d3': '4f60',
                'e2db': '4f7f', 'e29b': '516c', 'e2b0': '5176', 'e2ed': '51fa',
                'e2eb': '5206', 'e2f1': '5229', 'e307': '5230', 'e2ce': '5236',
                'e2e6': '524d', 'e2ea': '529b', 'e2a8': '52a0', 'e2a5': '5316',
                'e2bd': '5341', 'e302': '539f', 'e2df': '53bb', 'e2c7': '53c8',
                'e303': '53cd', 'e2ac': '53d1', 'e2f8': '53ea', 'e30b': '5404',
                'e29c': '5408', 'e2d7': '540c', 'e2d8': '540e', 'e306': '5411',
                'e2c5': '547d', 'e2b4': '56db', 'e2f9': '56e0', 'e2ca': '5730',
                'e2ef': '5916', 'e2bc': '591a', 'e301': '5929', 'e29a': '597d',
                'e2b7': '5b50', 'e2cc': '5b83', 'e2ee': '5b9a', 'e2ff': '5bb6',
                'e2e8': '5c0f', 'e2d4': '5c31', 'e2d5': '5c55', 'e2a1': '5de5',
                'e2a0': '5e73', 'e2fe': '5e74', 'e2c4': '5e76', 'e2c8': '5ea6',
                'e2ae': '5efa', 'e304': '5f62', 'e291': '5f88', 'e2e2': '5f97',
                'e2f2': '5fc3', 'e295': '6027', 'e2d9': '60c5', 'e2be': '60f3',
                'e2c3': '610f', 'e30d': '6210', 'e2ba': '6216', 'e2fa': '6240',
                'e29e': '628a', 'e2a7': '63d0', 'e2d2': '653f', 'e2ad': '6599',
                'e2cd': '65b0', 'e2f3': '65b9'
            };
            var regex = new RegExp(properties(replacements).map(RegExp.escape).join('|'), 'g');
            strUnicode = strUnicode.replace(regex, function($0) { return replacements[$0]; });
            strUnicode = strUnicode.replace('u0', '');
            str = tohanzi(strUnicode);
            bookreadercontent.innerText = str;
        }
    }

    function version_up_3_to_4() {
        var old_version = GM_getValue('black_list');
        if (!old_version) return;
        rwl_userData.data = unique(rwl_userData.data.concat(old_version.data));
        GM_setValue('rwl_userData', rwl_userData);
        GM_deleteValue('black_list');
        GM_deleteValue('rwl_userdata');
    }
})();