NodeSeek增强

自动签到、自动滚动翻页

目前為 2024-02-23 提交的版本,檢視 最新版本

// ==UserScript==
// @name         NodeSeek增强
// @namespace    http://www.nodeseek.com/
// @version      0.3-alpha
// @description  自动签到、自动滚动翻页
// @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_notification
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_getResourceText
// @grant        GM_addElement
// @grant        GM_addStyle
// @grant        unsafeWindow
// @run-at       document-end
// @license      GPL-3.0 License
// @supportURL   https://www.nodeseek.com/notification#/message?mode=talk&to=8110
// @homepageURL  https://www.nodeseek.com/post-36263-1
// ==/UserScript==

(function () {
    'use strict';

    const util = {
        clog(c) {
            console.group("%c %c [NodeSeek增强]", `background:url(${GM_info.script.icon}) center center no-repeat;background-size:12px;padding:3px`, "");
            console.log(c);
            console.groupEnd();
        },
        parseQuery(name) {
            let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
            let r = location.search.substr(1).match(reg);
            if (r != null) return (r[2]);
            return null;
        },
        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);
        },
        isHidden(el) {
            try {
                return el.offsetParent === null;
            } catch (e) {
                return false;
            }
        },
        query(selector) {
            if (Array.isArray(selector)) {
                let obj = null;
                for (let i = 0; i < selector.length; i++) {
                    let o = document.querySelector(selector[i]);
                    if (o) {
                        obj = o;
                        break;
                    }
                }
                return obj;
            }
            return document.querySelector(selector);
        },
        getAttributesByPrefix(element, prefix) {
            var attributes = element.attributes;
            var matchingAttributes = {};
            for (var attribute of attributes) {
                var attributeName = attribute.name;
                var attributeValue = attribute.value;

                if (attributeName.startsWith(prefix)) {
                    matchingAttributes[attributeName] = attributeValue;
                }
            }
            return matchingAttributes;
        },
        openLinkInNewTab(selector) {
            var allLinks = document.querySelectorAll(selector);

            allLinks.forEach(function (link) {
                link.setAttribute('target', '_blank');
            });
        }
    };

    const opts = {
        post: {
            pathPattern: /^\/(categories\/|page|award|$)/,
            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'
        }
    };

    let main = {
        // 初始化配置数据
        initValue() {
            let value = [{
                name: opts.setting.SETTING_SIGN_IN_STATUS,
                value: 0
            }];

            value.forEach((v) => {
                if (util.getValue(v.name) === undefined) {
                    util.setValue(v.name, v.value);
                }
            });
        },
        loginStatus: false,
        //检查是否登陆
        checkLogin() {
            if (document.querySelector('#nsk-right-panel-container>.user-card')) {
                this.loginStatus = true;
                util.clog('已登录');
            }
        },
        // 自动签到
        autoSignIn(rand) {
            if (!this.loginStatus) return

            let localTimezoneOffset = (new Date()).getTimezoneOffset();
            let beijingOffset = 8 * 60;
            let beijingTime = new Date(Date.now() + (localTimezoneOffset + beijingOffset) * 60 * 1000);
            let timeNow = `${beijingTime.getFullYear()}/${(beijingTime.getMonth() + 1)}/${beijingTime.getDate()}`,
                timeOld = util.getValue('menu_signInTime');
            if (!timeOld || timeOld != timeNow) { // 是新的一天
                util.setValue('menu_signInTime', timeNow); // 写入签到时间以供后续比较

                GM_xmlhttpRequest({
                    url: '/api/attendance?random=' + (rand || true),
                    method: 'POST',
                    timeout: 4000
                    , onload: function (res) {
                        if (res.status === 200) {
                            let json = JSON.parse(res.responseText);
                            if (json.success) {
                                GM_notification({ text: '签到成功!今天午饭+' + json.gain + '个鸡腿; 积攒了' + json.current + '个鸡腿了', timeout: 3500 });
                            }
                            else {
                                GM_notification({ text: '签到失败!' + json.message, timeout: 3500 });
                            }
                        }
                    }, onerror: function (err) {
                        util.clog('error');
                        util.clog(err)
                    }
                });
                util.clog(`[NodeSeek] 签到完成`);
            }
        },
        addSignTips() {
            let tip = document.createElement('div');
            tip.className = "nsplus-tip";
            let tip_p = document.createElement('p');
            tip_p.innerHTML = '今天你还没有签到哦!&emsp;【<a href="">随机抽个鸡腿<svg data-v-372de460="" class="iconpark-icon"><use data-v-372de460="" href="#chicken-leg"></use></svg></a>】&emsp;【<a href="">只要5个鸡腿</a>】&emsp;【<a href="">今天不再提示</a>】';
            tip.appendChild(tip_p);
            document.querySelector('#nsk-frame').before(tip);
        },
        quickComment() {
            let _this = this;
            document.querySelectorAll('div.comment-menu > div:nth-child(4) ').forEach(function (item) { item.onclick = function (e) { var md = document.querySelector('.md-editor'); md.style.position = 'fixed'; md.style.bottom = 0; md.style.width = '100%'; md.style.maxWidth = '720px'; md.style.zIndex = '999'; _this.addEditorCloseButton() } })
        },
        addEditorCloseButton() {
            var linkElement = document.createElement('a');

            // 设置属性
            linkElement.setAttribute('data-v-f5a54ae2', '');
            linkElement.setAttribute('href', 'javascript:void(0)');
            linkElement.setAttribute('title', '关闭');
            linkElement.setAttribute('class', 'editor-top-button');

            // 创建 <span> 元素
            var spanElement = document.createElement('span');
            spanElement.setAttribute('data-v-f5a54ae2', '');
            spanElement.setAttribute('class', 'i-icon i-icon-close');
            spanElement.innerHTML = '<svg width="16" height="16" viewBox="0 0 48 48" fill="none"><path d="M8 8L40 40" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path><path d="M8 40L40 8" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path></svg>';

            // 将元素组合起来
            linkElement.appendChild(spanElement);
            linkElement.addEventListener("click", function (e) {
                var md = document.querySelector('.md-editor'); md.style.position = ""; md.style.bottom = ""; md.style.maxWidth = "";
                this.remove();
            });
            document.querySelector('#editor-body > div.tab-select.window_header > a[title=全屏]').after(linkElement);
        },
        //新窗口打开帖子
        openPostInNewTab() {
            util.openLinkInNewTab('.post-title>a[href]');
        },
        //自动点击跳转页链接
        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("__key__")) {
                    item.closest(".post-list-item").remove()
                }
            });
        },
        //拉黑用户
        blockMemberDOMInsert() {
            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').innerText;
                            const blockBtn = document.createElement("a");
                            for (let k in dataVAttrs) {
                                blockBtn.setAttribute(k, dataVAttrs[k]);
                            };
                            blockBtn.onclick = function (e) { e.preventDefault(); main.blockMember(userName) };
                            blockBtn.className = "btn";
                            blockBtn.style.float = "left";
                            blockBtn.innerText = "拉黑";
                            pmButton.after(blockBtn);
                        }
                    }, 50);
                }))
            }))
        },
        // 黑名单
        blockMember(userName) {
            GM_xmlhttpRequest({
                url: "/api/block-list/add",
                method: 'POST',
                headers: {
                    "Content-Type": "application/json"
                },
                data: JSON.stringify({ "block_member_name": userName }),
                onload: function (res) {
                    if (res.status === 200) {
                        let result = JSON.parse(res.responseText);
                        if (result.success) {
                            let msg = '屏蔽用户【' + userName + '】成功!';
                            unsafeWindow.mscAlert(msg);
                            util.clog(msg);
                        } else {
                            let msg = '屏蔽用户【' + userName + '】失败!' + result.message;
                            unsafeWindow.mscAlert(msg);
                            util.clog(msg);
                        }
                    }
                }, onerror: function (err) {
                    util.clog(err);
                }
            });
        },

        // 自动翻页
        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;
                        GM_xmlhttpRequest({
                            url: nextUrl,
                            method: 'GET',
                            onload: function (res) {
                                if (res.status === 200) {
                                    let doc = new DOMParser().parseFromString(res.responseText, "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;
                            },
                            onerror: function (err) {
                                is_requesting = false;
                                util.clog(err);
                            }
                        });
                    }
                }
            });
        },
        // 滚动条事件
        windowScroll(fn1) {
            var beforeScrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop,
                fn = fn1 || function () { };
            setTimeout(function () { // 延时执行,避免刚载入到页面就触发翻页事件
                window.addEventListener('scroll', function (e) {
                    var 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].k} ${menu.text}(${menu.states[stateVal].v})`;
        },
        _menus: [{ name: opts.setting.SETTING_SIGN_IN_STATUS, callback: (name,states)=>main.switchMultiState(name,states), accessKey: '', text: '自动签到', states: [{ k: '❌', v: '关闭' }, { k: '🎲', v: '随机🍗' }, { k: '5️⃣', v: '5个🍗' }] }],//type:"b"--boolean;type:"m"--multi state
        _menuIds: [],
        registerMenus() {
            this._menuIds.forEach(function (id) {
                GM_unregisterMenuCommand(id);
            });
            this._menuIds = [];

            const _this = this;
            this._menus.forEach(function (menu) {
                let k = menu.name;
                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}
            `;

            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 });
        },
        init() {
            this.initValue();
            this.addPluginStyle();
            this.checkLogin();
            this.autoSignIn();//自动签到
            this.autoJump();//自动点击跳转页
            this.autoLoading();//无缝加载帖子和评论
            this.openPostInNewTab();//在新标签页打开帖子
            this.blockMemberDOMInsert();//拉黑用户
            this.blockPost();//屏蔽帖子
            this.quickComment();//快捷评论
            util.getValue(opts.setting.SETTING_SIGN_IN_STATUS) === 0 && this.addSignTips();//签到提示
            this.registerMenus();
            const css = GM_getResourceText("highlightStyle");
            GM_addStyle(css);
            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();}'
            });
        }
    }
    main.init();
})();