NodeSeek X

【原NodeSeek增强】自动签到、自动滚动翻页

2024-03-18 या दिनांकाला. सर्वात नवीन आवृत्ती पाहा.

// ==UserScript==
// @name         NodeSeek X
// @namespace    http://www.nodeseek.com/
// @version      0.3-beta.4
// @description  【原NodeSeek增强】自动签到、自动滚动翻页
// @author       dabao
// @match        *://www.nodeseek.com/*
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACz0lEQVR4Ae3B32tVdQAA8M85u7aVHObmzJVD0+ssiphstLEM62CBlCBEIAYhUoGGD/kiRUo+9CIEElFZgZJFSApBVhCUX2WFrVQKf5Qy26SgdK4pN7eZu+cbtyfJ/gLx83HD9SAhlEyXupiPhUSTeonRfNw1ws2aRJeN5jHcolFhJJ9M8Zj99piDTnv12SjzfzIb9dmrC7Pttt8ykjDVLsu8ZZ1GH1oqeDofJLtJh4fMEw3Y72jlCuEO2+W+sNJFr3vOZ1YIi8NIGA29hDWhGgZDJ2Rt2ZvZSBazmMUsZsPZ1qwVQmcYDNWwhtAbRsNIWJx6WLPDfgxNVkm9nR8hm+XduLba7F9RtcXztmUzyY/YJrUqNPvBYc0eSS3CwXxMl4WG7CarsyEuvU2HOkRNujSw3PosxR6DFurKxx3E/akFohPo0aDfEO61os5LdrtLVWG1TzxokifdiSH9GnTjuGhBqsWE39GOo3kVi8wsmeVW00SJ200zA9r0kFcdQzv+MKElVW/S+L5EE86pmUth3BV/SzCOCUjMVXMWzfsSYybVl1SlSlESkagpuOI1nzshFX1gyAF1UKhJEKOkJFVNXVBv+pJoBK1qBkh86z1/SaR+9o5zEgoDaloxsiSart6F1Bkl83ESHWEKvvEbqZJETaokgSH9hCk6cBLtSs6kDqEb/cZ0K+MnO0X/VdhRGUBZjzH9uA+HUl+a0BvmO+J7bVZSKWz1kehqhfe9oWalNoccDmW9JnyV+toxsy3PK3aY9Gx4gMp567ziV4WawpCXra+MEhZ5xqTtecVycxzXlxA22OK4ZYbt9LjvrM5PkNUp6zVPdNpBv1QKwt126Paxp8zwqXu8kG8pYZdHlT2Rvxo2aVG2ObyYn65UnXLKVULZZrP02ZRfCms1OmAXCSHRYqrLzuZFaDFV6s/8omuERs0Kl/LzITVTvTHDeXTD9eAftAsSYhXYOWUAAAAASUVORK5CYII=
// @require      https://cdn.staticfile.org/notie/4.3.1/notie.min.js
// @resource     notieStyle https://cdn.staticfile.org/notie/4.3.1/notie.min.css
// @resource     highlightStyle https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_notification
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_getResourceText
// @grant        GM_addElement
// @grant        GM_addStyle
// @grant        GM_openInTab
// @grant        GM_log
// @grant        unsafeWindow
// @run-at       document-end
// @license      GPL-3.0 License
// @supportURL   https://www.nodeseek.com/post-36263-1
// @homepageURL  https://www.nodeseek.com/post-36263-1
// ==/UserScript==

(function () {
    'use strict';

    const { version, author, name, icon } = GM_info.script;

    const BASE_URL = "https://www.nodeseek.com";

    const util = {
        clog(c) {
            console.group(`%c %c [${name}]-v${version} by ${author}`, `background:url(${icon}) center center no-repeat;background-size:12px;padding:3px`, "");
            GM_log(c);
            console.groupEnd();
        },
        getValue(name) {
            return GM_getValue(name);
        },
        setValue(name, value) {
            GM_setValue(name, value);
        },
        sleep(time) {
            return new Promise((resolve) => setTimeout(resolve, time));
        },
        addStyle(id, tag, css) {
            tag = tag || 'style';
            let doc = document, styleDom = doc.getElementById(id);
            if (styleDom) return;
            let style = doc.createElement(tag);
            style.rel = 'stylesheet';
            style.id = id;
            tag === 'style' ? style.innerHTML = css : style.href = css;
            document.head.appendChild(style);
        },
        getAttributesByPrefix(element, prefix) {
            const attributes = element.attributes;
            let matchingAttributes = {};
            for (let attribute of attributes) {
                const attributeName = attribute.name;
                const attributeValue = attribute.value;

                if (attributeName.startsWith(prefix)) {
                    matchingAttributes[attributeName] = attributeValue;
                }
            }
            return matchingAttributes;
        },
        data(element, key, value) {
            if (arguments.length < 2) {
                return undefined;
            }
            if (value != undefined) {
                element.dataset[key] = value;
            }
            return element.dataset[key];
        },
        post(url, data, headers, type) {
            url = !url.startsWith("http") ? BASE_URL + url : url;

            if (typeof data === 'object') {
                data = JSON.stringify(data);
            }
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "POST", url, headers, data,
                    responseType: type || 'json',
                    onload: (res) => resolve(res.response || res.responseText),
                    onerror: (err) => reject(err)
                });
            });
        },
        get(url, headers, type) {
            url = !url.startsWith("http") ? BASE_URL + url : url;

            return new Promise((resolve, reject) => {
                const requestObj = GM_xmlhttpRequest({
                    method: "GET", url, headers,
                    responseType: type || 'json',
                    onload: (res) => {
                        if (res.status === 204) {
                            requestObj.abort();
                        }
                        resolve(res.response || res.responseText);
                    },
                    onerror: (err) => reject(err)
                });
            });
        },
        getCurrentDate() {
            const localTimezoneOffset = (new Date()).getTimezoneOffset();
            const beijingOffset = 8 * 60;
            const beijingTime = new Date(Date.now() + (localTimezoneOffset + beijingOffset) * 60 * 1000);
            const timeNow = `${beijingTime.getFullYear()}/${(beijingTime.getMonth() + 1)}/${beijingTime.getDate()}`;
            return timeNow;
        },
        createElement(tagName, options = {}, childrens = [], doc = document) {
            if (Array.isArray(options)) {
                if (childrens.length !== 0) {
                    throw new Error("If options is an array, childrens should not be provided.");
                }
                childrens = options;
                options = {};
            }

            const { staticClass = '', class: dynamicClass = '', attrs = {}, on = {} } = options;

            const ele = doc.createElement(tagName);

            if (staticClass) {
                staticClass.split(' ').forEach(cls => ele.classList.add(cls.trim()));
            }
            if (dynamicClass) {
                dynamicClass.split(' ').forEach(cls => ele.classList.add(cls.trim()));
            }

            Object.entries(attrs).forEach(([key, value]) => {
                if (value !== undefined) ele.setAttribute(key, value);
            });

            Object.entries(on).forEach(([event, handler]) => {
                ele.addEventListener(event, handler);
            });

            childrens.forEach(child => {
                if (typeof child === 'string') {
                    child = doc.createTextNode(child);
                }
                ele.appendChild(child);
            });

            return ele;
        }

    };

    const opts = {
        post: {
            pathPattern: /^\/(categories\/|page|award|search|$)/,
            scrollThreshold: 200,
            nextPagerSelector: '.nsk-pager a.pager-next',
            postListSelector: 'ul.post-list',
            topPagerSelector: 'div.nsk-pager.pager-top',
            bottomPagerSelector: 'div.nsk-pager.pager-bottom',
        },
        comment: {
            pathPattern: /^\/post-/,
            scrollThreshold: 690,
            nextPagerSelector: '.nsk-pager a.pager-next',
            postListSelector: 'ul.comments',
            topPagerSelector: 'div.nsk-pager.post-top-pager',
            bottomPagerSelector: 'div.nsk-pager.post-bottom-pager',
        },
        setting: {
            SETTING_SIGN_IN_STATUS: 'setting_sign_in_status',
            SETTING_SIGN_IN_LAST_DATE: 'setting_sign_in_last_date',
            SETTING_SIGN_IN_IGNORE_DATE: 'setting_sign_in_ignore_date'
        }
    };

    const message = {
        info: (text) => notie.alert({ type: 'info', text: text }),
        success: (text) => notie.alert({ type: 'success', text: text }),
        warning: (text) => notie.alert({ type: 'warning', text: text }),
        error: (text) => notie.alert({ type: 'error', text: text })
    };

    const main = {
        // 初始化配置数据
        initValue() {
            const value = [
                { name: opts.setting.SETTING_SIGN_IN_STATUS, defaultValue: 0 },
                { name: opts.setting.SETTING_SIGN_IN_LAST_DATE, defaultValue: '1753/1/1' },
                { name: opts.setting.SETTING_SIGN_IN_IGNORE_DATE, defaultValue: '1753/1/1' }
            ];
            this.upgradeConfig();
            value.forEach((v) => util.getValue(v.name) === undefined && util.setValue(v.name, v.defaultValue));
        },
        // 升级配置项
        upgradeConfig() {
            const upgradeConfItem = (oldConfKey, newConfKey) => {
                if (util.getValue(oldConfKey) && util.getValue(newConfKey) === undefined) {
                    util.clog(`升级配置项 ${oldConfKey} 为 ${newConfKey}`);
                    util.setValue(newConfKey, util.getValue(oldConfKey));
                    GM_deleteValue(oldConfKey);
                }
            };
            upgradeConfItem('menu_signInTime', opts.setting.SETTING_SIGN_IN_LAST_DATE);
        },
        loginStatus: false,
        //检查是否登陆
        checkLogin() {
            if (unsafeWindow.meCard && unsafeWindow.meCard.logined) {
                this.loginStatus = true;
                util.clog(`当前登录用户 ${unsafeWindow.meCard.user.member_name} (ID ${unsafeWindow.meCard.user.member_id})`);
            }
        },
        // 自动签到
        autoSignIn(rand) {
            if (!this.loginStatus) return
            if (util.getValue(opts.setting.SETTING_SIGN_IN_STATUS) === 0) return;

            let timeNow = util.getCurrentDate(),
                timeOld = util.getValue(opts.setting.SETTING_SIGN_IN_LAST_DATE);
            if (!timeOld || timeOld != timeNow) { // 是新的一天
                util.setValue(opts.setting.SETTING_SIGN_IN_LAST_DATE, timeNow); // 写入签到时间以供后续比较
                this.signInRequest(rand);
            }
        },
        // 重新签到
        reSignIn() {
            if (!this.loginStatus) return;
            if (util.getValue(opts.setting.SETTING_SIGN_IN_STATUS) === 0) {
                unsafeWindow.mscAlert('提示', this.getMenuStateText(this._menus[0], 0) + ' 状态时不支持重新签到!');
                return;
            }

            util.setValue(opts.setting.SETTING_SIGN_IN_LAST_DATE, '1753/1/1');
            location.reload();
        },
        addSignTips() {
            if (!this.loginStatus) return
            if (util.getValue(opts.setting.SETTING_SIGN_IN_STATUS) !== 0) return;

            const timeNow = util.getCurrentDate();
            const { SETTING_SIGN_IN_IGNORE_DATE, SETTING_SIGN_IN_LAST_DATE } = opts.setting;
            const timeIgnore = util.getValue(SETTING_SIGN_IN_IGNORE_DATE);
            const timeOld = util.getValue(SETTING_SIGN_IN_LAST_DATE);

            if (timeNow === timeIgnore || timeNow === timeOld) return;

            const _this = this;
            let tip = util.createElement("div", { staticClass: 'nsplus-tip' });
            let tip_p = util.createElement('p');
            tip_p.innerHTML = '今天你还没有签到哦!&emsp;【<a class="sign_in_btn" data-rand="true" href="javascript:;">随机抽个鸡腿</a>】&emsp;【<a class="sign_in_btn" data-rand="false" href="javascript:;">只要5个鸡腿</a>】&emsp;【<a id="sign_in_ignore" href="javascript:;">今天不再提示</a>】';
            tip.appendChild(tip_p);
            tip.querySelectorAll('.sign_in_btn').forEach(function (item) {
                item.addEventListener("click", function (e) {
                    const rand = util.data(this, 'rand');
                    _this.signInRequest(rand);
                    tip.remove();
                    util.setValue(opts.setting.SETTING_SIGN_IN_LAST_DATE, timeNow); // 写入签到时间以供后续比较
                })
            });
            tip.querySelector('#sign_in_ignore').addEventListener("click", function (e) {
                tip.remove();
                util.setValue(opts.setting.SETTING_SIGN_IN_IGNORE_DATE, timeNow);
            });

            document.querySelector('#nsk-frame').before(tip);
        },
        signInRequest(rand) {
            util.post('/api/attendance?random=' + (rand || true), {}, { "Content-Type": "application/json" }, '').then(function (json) {
                if (json.success) {
                    message.success('签到成功!今天午饭+' + json.gain + '个鸡腿; 积攒了' + json.current + '个鸡腿了');
                    //GM_notification({ text: '签到成功!今天午饭+' + json.gain + '个鸡腿; 积攒了' + json.current + '个鸡腿了', timeout: 3500 });
                }
                else {
                    message.info(json.message);
                    //GM_notification({ text: json.message, timeout: 3500 });
                }
            }).catch(function (err) {
                util.clog(err)
            });
            util.clog(`[${name}] 签到完成`);
        },
        is_show_quick_comment: false,
        quickComment() {
            if (!this.loginStatus) return

            if (!opts.comment.pathPattern.test(location.pathname)) return;
            const _this = this;
            const onClick = (e) => {
                if (_this.is_show_quick_comment) {
                    return;
                }
                e.preventDefault();
                const md = document.querySelector('.md-editor');
                md.style.position = 'fixed'; md.style.top = '50%'; md.style.left = '50%'; md.style.transform = 'translate(-50%, -50%)'; md.style.margin = '30px 0'; md.style.width = '100%'; md.style.maxWidth = '720px'; md.style.zIndex = '999';
                addEditorCloseButton();
                _this.is_show_quick_comment = true;
            };
            const commentDiv = document.querySelector('#fast-nav-button-group #back-to-parent').cloneNode(true);
            commentDiv.id = 'back-to-comment';
            commentDiv.innerHTML = '<svg class="iconpark-icon" style="width: 24px; height: 24px;"><use href="#comments"></use></svg>';
            commentDiv.addEventListener("click", onClick);
            document.querySelector('#back-to-parent').before(commentDiv);
            document.querySelectorAll('div.comment-menu > div:nth-last-child(1),div.comment-menu > div:nth-last-child(2) ').forEach(function (item) { item.addEventListener("click", onClick, true); });

            function addEditorCloseButton() {
                const fullScreenToolbar = document.querySelector('#editor-body .window_header > :last-child');
                const cloneToolbar = fullScreenToolbar.cloneNode(true);
                cloneToolbar.setAttribute('title', '关闭');
                cloneToolbar.querySelector('span').classList.replace('i-icon-full-screen-one', 'i-icon-close');
                cloneToolbar.querySelector('span').innerHTML = '<svg width="16" height="16" viewBox="0 0 48 48" fill="none"><path d="M8 8L40 40" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path><path d="M8 40L40 8" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
                cloneToolbar.addEventListener("click", function (e) {
                    document.querySelector('.md-editor').style = "";
                    this.remove();
                    _this.is_show_quick_comment = false;
                });
                fullScreenToolbar.after(cloneToolbar);
            }
        },

        //新窗口打开帖子
        openPostInNewTab() {
            if (document.querySelector('a[href^="/post-"]')) {
                document.querySelectorAll('a[href^="/post-"]').forEach(function (item) {
                    if (item.classList.contains("pager-prev") || item.classList.contains("pager-pos") || item.classList.contains("pager-next")) {
                        return;
                    }
                    item.target || (item.target = "_blank");
                });
            }
        },
        //自动点击跳转页链接
        autoJump() {
            if (!/^\/jump/.test(location.pathname)) return;
            document.querySelector('.btn').click();
        },
        blockPost(ele) {
            ele = ele || document;
            ele.querySelectorAll('.post-title>a[href]').forEach(function (item) {
                if (item.textContent.toLowerCase().includes("__keys__")) {
                    item.closest(".post-list-item").classList.add('blocked-post')
                }
            });
        },
        //屏蔽用户
        blockMemberDOMInsert() {
            if (!this.loginStatus) return;

            const _this = this;
            Array.from(document.querySelectorAll(".post-list .post-list-item,.content-item")).forEach((function (t, n) {
                var r = t.querySelector('.avatar-normal');
                r.addEventListener("click", (function (n) {
                    n.preventDefault();
                    let intervalId = setInterval(async () => {
                        const userCard = document.querySelector('div.user-card.hover-user-card');
                        const pmButton = document.querySelector('div.user-card.hover-user-card a.btn');
                        if (userCard && pmButton) {
                            clearInterval(intervalId);
                            const dataVAttrs = util.getAttributesByPrefix(userCard, 'data-v');
                            const userName = userCard.querySelector('a.Username').textContent;
                            dataVAttrs.style = "float:left; background-color:rgba(0,0,0,.3)";
                            const blockBtn = util.createElement("a", {
                                staticClass: "btn", attrs: dataVAttrs, on: {
                                    click: function (e) {
                                        e.preventDefault();
                                        unsafeWindow.mscConfirm(`确定要屏蔽“${userName}”吗?`, '你可以在本站的 设置=>屏蔽用户 中解除屏蔽', function () { blockMember(userName); })
                                    }
                                }
                            }, ["屏蔽"]);
                            pmButton.after(blockBtn);
                        }
                    }, 50);
                }))
            }))
            function blockMember(userName) {
                util.post("/api/block-list/add", { "block_member_name": userName }, { "Content-Type": "application/json" }, '').then(function (data) {
                    if (data.success) {
                        let msg = '屏蔽用户【' + userName + '】成功!';
                        unsafeWindow.mscAlert(msg);
                        util.clog(msg);
                    } else {
                        let msg = '屏蔽用户【' + userName + '】失败!' + data.message;
                        unsafeWindow.mscAlert(msg);
                        util.clog(msg);
                    }
                }).catch(function (err) {
                    util.clog(err);
                });
            }
        },

        addLevelTag() {//添加等级标签
            if (!this.loginStatus) return;
            if (!opts.comment.pathPattern.test(location.pathname)) return;

            this.getUserInfo(unsafeWindow.__config__.postData.op.uid).then((user) => {
                const span = util.createElement("span", { staticClass: `nsk-badge role-tag user-level user-lv${user.rank}`, attrs: { title: `Joined ${user.created_at_str}` } }, [util.createElement("span", [`Lv ${user.rank}`])]);

                const authorLink = document.querySelector('#nsk-body .nsk-post .nsk-content-meta-info .author-info>a');
                if (authorLink != null) {
                    authorLink.after(span);
                }
            });
        },
        getUserInfo(uid) {
            return new Promise((resolve, reject) => {
                util.get(`/api/account/getInfo/${uid}`, {}, 'json').then((data) => {
                    if (!data.success) {
                        util.clog(data);
                        return;
                    }
                    resolve(data.detail);
                }).catch((err) => reject(err));
            })
        },
        userCardEx() {
            if (!this.loginStatus) return;

            const updateNotificationElement = (element, href, iconHref, text, count) => {
                element.querySelector("a").setAttribute("href", `${href}`);
                element.querySelector("a > svg > use").setAttribute("href", `${iconHref}`)
                element.querySelector("a > :nth-child(2)").textContent = `${text} `;
                element.querySelector("a > :last-child").textContent = count;
                if (count > 0) {
                    element.querySelector("a > :last-child").classList.add("notify-count");
                }
                return element;
            };

            const userCard = document.querySelector(".user-card .user-stat");
            const lastElement = userCard.querySelector(".stat-block:first-child > :last-child");
            const unViewedCount = unsafeWindow.__config__.user.unViewedCount;

            if (lastElement.querySelector("a > .notify-count:last-child")) {
                lastElement.querySelector("a > .notify-count:last-child").classList.remove("notify-count");
            }

            const atMeElement = lastElement.cloneNode(true);
            updateNotificationElement(atMeElement, "/notification#/atMe", "#at-sign", "我", unViewedCount.atMe);
            lastElement.after(atMeElement);

            const msgElement = lastElement.cloneNode(true);
            updateNotificationElement(msgElement, "/notification#/message?mode=list", "#envelope-one", "私信", unViewedCount.message);
            userCard.querySelector(".stat-block:last-child").append(msgElement);

            updateNotificationElement(lastElement, "/notification#/reply", "#remind-6nce9p47", "回复", unViewedCount.reply);
        },
        // 自动翻页
        autoLoading() {
            let opt = {};
            if (opts.post.pathPattern.test(location.pathname)) { opt = opts.post; }
            else if (opts.comment.pathPattern.test(location.pathname)) { opt = opts.comment; }
            else { return; }
            let is_requesting = false;
            let _this = this;
            this.windowScroll(function (direction, e) {
                if (direction === 'down') { // 下滑才准备翻页
                    let scrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
                    if (document.documentElement.scrollHeight <= document.documentElement.clientHeight + scrollTop + opt.scrollThreshold && !is_requesting) {
                        if (!document.querySelector(opt.nextPagerSelector)) return;
                        let nextUrl = document.querySelector(opt.nextPagerSelector).attributes.href.value;
                        is_requesting = true;
                        util.get(nextUrl, {}, 'text').then(function (data) {
                            let doc = new DOMParser().parseFromString(data, "text/html");
                            _this.blockPost(doc);//过滤帖子
                            document.querySelector(opt.postListSelector).append(...doc.querySelector(opt.postListSelector).childNodes);
                            document.querySelector(opt.topPagerSelector).innerHTML = doc.querySelector(opt.topPagerSelector).innerHTML;
                            document.querySelector(opt.bottomPagerSelector).innerHTML = doc.querySelector(opt.bottomPagerSelector).innerHTML;
                            history.pushState(null, null, nextUrl);
                            is_requesting = false;
                        }).catch(function (err) {
                            is_requesting = false;
                            util.clog(err);
                        });
                    }
                }
            });
        },
        // 滚动条事件
        windowScroll(fn1) {
            let beforeScrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop,
                fn = fn1 || function () { };
            setTimeout(function () { // 延时执行,避免刚载入到页面就触发翻页事件
                window.addEventListener('scroll', function (e) {
                    const afterScrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop,
                        delta = afterScrollTop - beforeScrollTop;
                    if (delta == 0) return false;
                    fn(delta > 0 ? 'down' : 'up', e);
                    beforeScrollTop = afterScrollTop;
                }, false);
            }, 1000)
        },
        switchMultiState(stateName, states) {//多态顺序切换
            let currState = util.getValue(stateName);
            currState = (currState + 1) % states.length;
            util.setValue(stateName, currState);
            this.registerMenus();
        },
        getMenuStateText(menu, stateVal) {
            return `${menu.states[stateVal].s1} ${menu.text}(${menu.states[stateVal].s2})`;
        },
        _menus: [
            { name: opts.setting.SETTING_SIGN_IN_STATUS, callback: (name, states) => main.switchMultiState(name, states), accessKey: '', text: '自动签到', states: [{ s1: '❌', s2: '关闭' }, { s1: '🎲', s2: '随机🍗' }, { s1: '📌', s2: '5个🍗' }] },
            { name: 're_sign_in', callback: (name, states) => main.reSignIn(), accessKey: '', text: '🔂 重新签到', states: [] },
            { name: 'advanced_settings', callback: (name, states) => unsafeWindow.mscAlert('全力施工中...'), accessKey: '', text: '⚙️ 高级设置', states: [] },
            { name: 'feedback', callback: (name, states) => GM_openInTab('https://greasyfork.org/zh-CN/scripts/479426/feedback', { active: true, insert: true, setParent: true }), accessKey: '', text: '💬 反馈 & 建议', states: [] }
        ],
        _menuIds: [],
        registerMenus() {
            this._menuIds.forEach(function (id) {
                GM_unregisterMenuCommand(id);
            });
            this._menuIds = [];

            const _this = this;
            this._menus.forEach(function (menu) {
                let k = menu.text;
                if (menu.states.length > 0) {
                    k = _this.getMenuStateText(menu, util.getValue(menu.name));
                }
                let menuId = GM_registerMenuCommand(k, function () { menu.callback(menu.name, menu.states) });
                _this._menuIds.push(menuId);
            });
        },
        addPluginStyle() {
            let style = `
                .notie-container{ opacity: 0.8; }
            .nsplus-tip { background-color: rgba(255, 217, 0, 0.8); border: 0px solid black;  padding: 10px; text-align: center;animation: blink 5s cubic-bezier(.68,.05,.46,.96) infinite;}
            /* @keyframes blink{ 0%{background-color: red;} 25%{background-color: yellow;} 50%{background-color: blue;} 75%{background-color: green;} 100%{background-color: red;} } */
            .nsplus-tip p,.nsplus-tip p a { color: #f00 }
            .nsplus-tip p a:hover {color: #0ff}
            #back-to-comment{display:flex;}
            #fast-nav-button-group .nav-item-btn:nth-last-child(4){bottom:120px;}
            body.light-layout .post-list .post-title a:visited{color:#681da8}
            body.dark-layout .post-list .post-title a:visited {color:#999}
            .role-tag.user-level.user-lv0 {background-color: rgb(199 194 194); border: 1px solid rgb(199 194 194); color: #fafafa;}
            .role-tag.user-level.user-lv1 {background-color: #ff9400; border: 1px solid #ff9400; color: #fafafa;}
            .role-tag.user-level.user-lv2 {background-color: #ff9400; border: 1px solid #ff9400; color: #fafafa;}
            .role-tag.user-level.user-lv3 {background-color: #ff3a55; border: 1px solid #ff3a55; color: #fafafa;}
            .role-tag.user-level.user-lv4 {background-color: #ff3a55; border: 1px solid #ff3a55; color: #fafafa;}
            .role-tag.user-level.user-lv5 {background-color: #de00ff; border: 1px solid #de00ff; color: #fafafa;}
            .role-tag.user-level.user-lv6 {background-color: #de00ff; border: 1px solid #de00ff; color: #fafafa;}
            .role-tag.user-level.user-lv7 {background-color: #ff0000; border: 1px solid #ff0000; color: #fafafa;}
            .role-tag.user-level.user-lv8 {background-color: #3478f7; border: 1px solid #3478f7; color: #fafafa;}
            `;

            if (document.head) {
                util.addStyle('notie-style', 'style', GM_getResourceText('notieStyle'));
                util.addStyle('nsplus-style', 'style', style);
            }

            const headObserver = new MutationObserver(() => {
                util.addStyle('notie-style', 'style', GM_getResourceText('notieStyle'));
                util.addStyle('nsplus-style', 'style', style);
            });
            headObserver.observe(document.head, { childList: true, subtree: true });
        },
        addPluginScript() {
            GM_addElement('script', {
                src: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js'
            });
            GM_addElement('script', {
                textContent: 'window.onload = function(){hljs.highlightAll();}'
            });
            GM_addElement("script", { textContent: `!function(e){var t,n,d,o,i,a,r='<svg><symbol id="envelope-one" viewBox="0 0 48 48" fill="none"><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="currentColor" d="M36 16V8H4v24h8" data-follow-stroke="currentColor"/><path stroke-linejoin="round" stroke-width="4" stroke="currentColor" d="M12 40h32V16H12v24Z" data-follow-stroke="currentColor"/><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="currentColor" d="m12 16 16 12 16-12" data-follow-stroke="currentColor"/><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="currentColor" d="M32 16H12v15" data-follow-stroke="currentColor"/><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="currentColor" d="M44 31V16H24" data-follow-stroke="currentColor"/></symbol><symbol id="at-sign" viewBox="0 0 48 48" fill="none"><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="currentColor" d="M44 24c0-11.046-8.954-20-20-20S4 12.954 4 24s8.954 20 20 20v0c4.989 0 9.55-1.827 13.054-4.847" data-follow-stroke="currentColor"/><path stroke-linejoin="round" stroke-width="4" stroke="currentColor" d="M24 32a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z" data-follow-stroke="currentColor"/><path stroke-linejoin="round" stroke-linecap="round" stroke-width="4" stroke="currentColor" d="M32 24a6 6 0 0 0 6 6v0a6 6 0 0 0 6-6m-12 1v-9" data-follow-stroke="currentColor"/></symbol></svg>';function c(){i||(i=!0,d())}t=function(){var e,t,n;(n=document.createElement("div")).innerHTML=r,r=null,(t=n.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",e=t,(n=document.body).firstChild?(t=n.firstChild).parentNode.insertBefore(e,t):n.appendChild(e))},document.addEventListener?["complete","loaded","interactive"].indexOf(document.readyState)>-1?setTimeout(t,0):(n=function(){document.removeEventListener("DOMContentLoaded",n,!1),t()},document.addEventListener("DOMContentLoaded",n,!1)):document.attachEvent&&(d=t,o=e.document,i=!1,(a=function(){try{o.documentElement.doScroll("left")}catch(e){return void setTimeout(a,50)}c()})(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,c())})}(window);` });
        },
        init() {
            this.initValue();
            this.addPluginStyle();
            this.checkLogin();
            this.autoSignIn();//自动签到
            this.addSignTips();//签到提示
            this.autoJump();//自动点击跳转页
            this.autoLoading();//无缝加载帖子和评论
            this.openPostInNewTab();//在新标签页打开帖子
            this.blockMemberDOMInsert();//屏蔽用户
            this.blockPost();//屏蔽帖子
            this.quickComment();//快捷评论
            this.addLevelTag();//添加等级标签
            this.userCardEx();//用户卡片扩展
            this.registerMenus();
            const css = GM_getResourceText("highlightStyle");
            GM_addStyle(css);
            this.addPluginScript();
        }
    }
    main.init();
})();