Soul++

让魂+论坛变得更好用一些

2021-10-03 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

// ==UserScript==
// @name            Soul++
// @namespace       SoulPlusPlus
// @version         0.6
// @description     让魂+论坛变得更好用一些
// @run-at          document-start
// @author          镜花水中捞月
// @homepage        https://github.com/FetchTheMoon
// @icon64          https://cdn.jsdelivr.net/gh/FetchTheMoon/UserScript/LOGO.png
// @supportURL      https://github.com/FetchTheMoon/UserScript/issues
// --------------------------------------------
// @match           https://*.spring-plus.net/*
// @match           https://*.summer-plus.net/*
// @match           https://*.soul-plus.net/*
// @match           https://*.south-plus.net/*
// @match           https://*.north-plus.net/*
// @match           https://*.snow-plus.net/*
// @match           https://*.level-plus.net/*
// @match           https://*.white-plus.net/*
// @match           https://*.imoutolove.me/*
// @match           https://*.south-plus.org/*
// --------------------------------------------
// @match           https://spring-plus.net/*
// @match           https://summer-plus.net/*
// @match           https://soul-plus.net/*
// @match           https://south-plus.net/*
// @match           https://north-plus.net/*
// @match           https://snow-plus.net/*
// @match           https://level-plus.net/*
// @match           https://white-plus.net/*
// @match           https://imoutolove.me/*
// @match           https://south-plus.org/*
// --------------------------------------------
// @grant           GM_getValue
// @grant           GM_setValue
// @grant           GM_listValues
// @grant           GM_registerMenuCommand
// @grant           GM_unregisterMenuCommand
// @grant           GM_notification
// @grant           GM_deleteValue
// @grant           unsafeWindow
// --------------------------------------------
// @license         GPL-3.0 License
// ==/UserScript==


//##############################################################
// 注册选项
//##############################################################
'use strict';
const PageType = Object.freeze({
    THREADS_PAGE: Symbol("普通主题列表"),
    PIC_WALL_PAGE: Symbol("图墙区主题列表"),
    POSTS_PAGE: Symbol("帖子列表"),
    SEARCH_RESULT: Symbol("搜索结果")
});

const OptionType = Object.freeze({
    CLICK: Symbol("点击生效一次的菜单项"),
    SWITCH: Symbol("点击切换开关的菜单项"),
});


let menu = [
    {
        key: "loadingBoughtPostWithoutRefresh",
        title: "免刷新显示购买内容",
        defaultValue: true,
        optionType: OptionType.SWITCH,
    },
    {
        key: "automaticTaskCollection",
        title: "自动领取并完成论坛任务",
        defaultValue: true,
        optionType: OptionType.SWITCH,
    },
    {
        key: "dynamicLoadingThreads",
        title: "无缝加载下一页的帖子",
        optionType: OptionType.SWITCH,
    },
    {
        key: "dynamicLoadingPosts",
        title: "无缝加载下一页的楼层",
        optionType: OptionType.SWITCH,
    },
    {
        key: "dynamicLoadingSearchResult",
        title: "无缝加载搜索页结果",
        optionType: OptionType.SWITCH,
    },
    {
        key: "dynamicLoadingPicWall",
        title: "无缝加载图墙区帖子",
        optionType: OptionType.SWITCH,
    },
    {
        key: "BlockSearchResultFromADForum",
        title: "屏蔽网赚区搜索结果",
        optionType: OptionType.SWITCH,
    },
    {
        key: "hidePostImage",
        title: "(sfw)安全模式 - 折叠帖子图片",
        optionType: OptionType.SWITCH,
    },
    {
        key: "hideUserAvatar",
        title: "(sfw)安全模式 - 替换用户头像为默认",
        optionType: OptionType.SWITCH,
    },
    {
        key: "highlightLastViewedThread",
        title: "在板块页面高亮刚才浏览的帖子",
        defaultValue: true,
        optionType: OptionType.SWITCH,
    },

];


class MenuOption {
    constructor(cfg) {
        this.key = cfg.key;
        this.title = cfg.title;
        this.defaultValue = cfg.defaultValue;
        this.onclick = cfg.onclick;
        if (GM_listValues().indexOf(this.key) === -1) GM_setValue(this.key, this.defaultValue ? this.defaultValue : false);
    }

    registerOption() {
        this.optionId = GM_registerMenuCommand(this.title, this.onclick);
    }

    unregisterOption() {
        GM_unregisterMenuCommand(this.optionId);
    }
}


class SwitchOption extends MenuOption {
    constructor(cfg) {
        super(cfg);
    }

    registerOption() {
        this.optionId = GM_registerMenuCommand(
            `${GM_getValue(this.key) ? '✅' : '❌'} ${this.title}`,
            this.onclick ? this.onclick : () => {
                GM_setValue(this.key, !GM_getValue(this.key));
                registerAllOptions();
                GM_notification(
                    {
                        text: `${this.title}已${GM_getValue(this.key) ? "✅启用" : "❌禁用"}\n刷新网页后生效`,
                        timeout: 2000,
                        onclick: () => location.reload(),
                        // 这个ondone不生效?
                        // ondone: ()=>location.reload()
                    });
                setTimeout(() => location.reload(), 2000);
            });
    }
}

function registerAllOptions() {
    menu.forEach((item) => {
        if (item.instance) item.instance.unregisterOption();
        switch (item.optionType) {
            case OptionType.SWITCH:
                item.instance = new SwitchOption(item);
                break;
            case OptionType.CLICK:
                item.instance = new MenuOption(item);
                break;
            default :
                item.instance = new MenuOption(item);
        }

        item.instance.registerOption();
    })
}


function getElementByXpath(from, xpath) {
    return from.evaluate(xpath, from, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}


//##############################################################
// 功能
//##############################################################

function loadingBoughtPostWithoutRefresh(target = document) {
    let buyButtons = target.querySelectorAll(".quote.jumbotron>.btn.btn-danger")
    buyButtons.forEach(button => {
        // 获取GET购买地址
        const urlRegex = /location\.href='(.+)'/
        const buyUrl = button.getAttribute("onclick");
        if (buyUrl === null) return;
        let m = buyUrl.match(urlRegex);
        if (m === null) return;
        let url = m[1];
        // 避免点击按钮的时候跳转,删掉这个属性
        button.setAttribute("onclick", "null");
        // 拿到帖子ID
        let postContainer = button.closest(".tpc_content .f14")
        let post_id = postContainer.getAttribute("id");
        // 添加点击事件,用fetch发送请求,然后读取页面再直接修改当前页面
        let customPurchase = (e => {
            e.stopPropagation();
            let btn = e.target;
            btn.setAttribute("value", "正在购买……请稍等………");
            try {
                fetch(url,
                    {
                        credentials: 'include',
                        mode: "no-cors"
                    })
                    .then(resp => resp.text())
                    .then(text => {
                        if (!text.includes("操作完成")) {
                            alert("购买失败!");
                            return;
                        }
                        let threadID = postContainer.getAttribute("tid");
                        let pg = postContainer.getAttribute("page");
                        let resultURL = `./read.php?tid=${threadID}&page=${pg}`;
                        fetch(resultURL, {
                            credentials: 'include',
                            mode: "no-cors"
                        }).then(resp => resp.text())
                            .then(html => {
                                let dummy = document.createElement("html");
                                dummy.innerHTML = html;
                                if (GM_getValue("hidePostImage")) {
                                    hidePostImage(dummy);
                                }
                                let purchased = dummy.querySelector("#" + post_id);
                                let notPurchased = document.querySelector("#" + post_id);
                                notPurchased.parentNode.replaceChild(purchased, notPurchased);

                            });

                        btn.style.display = "none";

                    });

            } catch (error) {
                alert(`发送请求出错,购买失败!\n${error}`);
                console.log('Request Failed', error);
            }
        })

        button.addEventListener("click", customPurchase);
    });
}

function hidePostImage(target = document) {
    let thread_user_post_images = target.querySelectorAll(".t5.t2 .r_one img");

    thread_user_post_images.forEach(img => {
        // 避免折叠论坛表情
        const emojiPathReg = /images\/post\/smile\//;
        if (img.getAttribute("src").match(emojiPathReg)) {
            return
        }
        // 避免折叠论坛自带的文件图标
        const fileIconPathReg = /images\/colorImagination\/file\//;
        if (img.getAttribute("src").match(fileIconPathReg)) {
            return
        }

        // 避免重复处理
        let p = img.parentNode;
        if (p.getAttribute("class") === "spp-img-mask") return;

        // 创建包裹元素
        let wrapper = document.createElement('div');
        wrapper.setAttribute("class", "spp-img-mask");
        wrapper.style.display = "grid";
        wrapper.style.gridTemplateColumns = "auto";

        // 将父元素下的图片元素替换成包裹元素
        img.parentNode.replaceChild(wrapper, img);

        // 将图片元素当成子元素放入包裹元素
        wrapper.appendChild(img);

        // 隐藏图片
        img.style.display = "none";
        img.style.textAlign = "center";

        // 添加类名
        img.setAttribute("class", "spp-thread-imgs");

        // 包裹元素样式
        wrapper.style.borderStyle = "dashed";
        wrapper.style.width = "auto";
        wrapper.style.height = "20";
        wrapper.style.textAlign = "center";
        wrapper.style.verticalAlign = "center";

        // 创建遮罩小人儿表情
        let icon_hide = document.createElement("img");
        icon_hide.setAttribute("src", "images/post/smile/smallface/face106.gif");

        let icon_show = document.createElement("img");
        icon_show.setAttribute("src", "images/post/smile/smallface/face109.gif");
        icon_show.style.display = "none";

        // 创建遮罩文本
        let tip = document.createElement("span");
        let tip_text = document.createElement("span");
        tip_text.innerText = "看看是啥";

        // 凑一堆儿来
        tip.appendChild(icon_hide);
        tip.appendChild(icon_show);
        tip.appendChild(tip_text);

        // 添加类名
        icon_hide.setAttribute("class", "spp-img-mask-icon-hide");
        icon_show.setAttribute("class", "spp-img-mask-icon-show");
        tip.setAttribute("class", "ssp-img-mask-text");

        // 插入元素
        wrapper.insertBefore(tip, img);

        // 事件监听
        wrapper.addEventListener("mouseenter", (e) => {
            e.stopPropagation();
            e.target.querySelector(".spp-thread-imgs").style.display = "";
            e.target.querySelector(".spp-img-mask-icon-hide").style.display = "none";
            e.target.querySelector(".spp-img-mask-icon-show").style.display = "";
        });
        wrapper.addEventListener("mouseleave", (e) => {
            e.stopPropagation();
            e.target.querySelector(".spp-thread-imgs").style.display = "none";
            e.target.querySelector(".spp-img-mask-icon-hide").style.display = "";
            e.target.querySelector(".spp-img-mask-icon-show").style.display = "none";
        });


    });
}

function hideUserAvatar(target = document) {
    let user_avatars = target.querySelectorAll(".user-pic img");
    user_avatars.forEach((avatar) => {
        let src = avatar.getAttribute("src");
        if (src !== "images/face/none.gif") {
            // 创建包裹元素
            let wrapper = document.createElement('div');
            wrapper.setAttribute("class", "spp-avatar-mask");

            // 创建一个假头像
            let fakeAvatarElement = document.createElement("img");
            fakeAvatarElement.setAttribute("src", "images/face/none.gif");
            fakeAvatarElement.style.borderStyle = "dashed";
            fakeAvatarElement.style.borderRadius = "3";
            fakeAvatarElement.style.borderWidth = "3px";
            fakeAvatarElement.style.borderColor = "Orange";

            // 替换包裹元素
            avatar.parentNode.replaceChild(wrapper, avatar);

            // 将假头像和真头像插到包裹元素中
            wrapper.appendChild(avatar);
            wrapper.appendChild(fakeAvatarElement);

            // 隐藏真头像
            avatar.style.display = "none";

            // 设置类名
            fakeAvatarElement.setAttribute("class", "spp-avatar-fake");
            avatar.setAttribute("class", "spp-avatar-real");

            // 事件监听
            wrapper.addEventListener("mouseenter", (e) => {
                e.stopPropagation();
                e.target.querySelector(".spp-avatar-fake").style.display = "none";
                e.target.querySelector(".spp-avatar-real").style.display = "";
            });

            wrapper.addEventListener("mouseleave", (e) => {
                e.stopPropagation();
                e.target.querySelector(".spp-avatar-fake").style.display = "";
                e.target.querySelector(".spp-avatar-real").style.display = "none";
            });

        }
    });
}

function dynamicLoadingNextPage(pageType) {

    class NextPageLoader {

        constructor() {
            this.isFetching = false;
            this.nextPageDummy = null;
        }

        GetURLDummy(url) {
            this.nextPageDummy = document.createElement("html");
            this.isFetching = true;
            return fetch(url, {credentials: 'include', mode: "no-cors"})
                .then(response => response.text())

        }

        AppendNextPageItems(itemSelector, divider) {

            let postsFragment = document.createDocumentFragment();
            this.nextPageDummy.querySelectorAll(itemSelector).forEach(ele => postsFragment.appendChild(ele));
            // 追加下一页的所有子项追加到分割线下面
            divider.parentNode.appendChild(postsFragment);
        }

        UpdatePageList() {
            // 主动更新帖子列表上下方的当前页码数
            let pagesOld = document.querySelectorAll(".pages");
            let pagesNew = this.nextPageDummy.querySelectorAll(".pages");
            for (let i = 0; i < pagesOld.length; i++) {
                pagesOld[i].parentNode.replaceChild(pagesNew[i], pagesOld[i]);
            }
        }

    }


    function getNextPageUrl() {
        let pageNum = document.querySelector(".pages b").parentNode;
        let url = pageNum.nextSibling.firstChild.getAttribute("href");
        if (pageNum.nextSibling.nextSibling.firstChild.getAttribute("class") === "pagesone") return null;
        if (document.URL.includes(url)) return null;
        return url;
    }


    function makeDivider(itemsSelector, dividerMaker) {
        let divider = dividerMaker();
        let allItem = document.querySelectorAll(itemsSelector);
        let lastItem = allItem[allItem.length - 1];
        lastItem.parentNode.appendChild(divider);
        return divider;
    }

    let nextPageLoader;
    let nextPageURL;
    nextPageLoader = nextPageLoader || new NextPageLoader()
    // 处理搜索结果页面
    if (pageType === PageType.SEARCH_RESULT) {
        document.addEventListener('wheel', (e) => {
            e.stopPropagation();
            const itemListSelector = ".tr3.tac";
            if (e.deltaY < 0 || nextPageLoader.isFetching) return;
            if (!nextPageLoader.nextPageDummy) {
                nextPageURL = getNextPageUrl();
                if (!nextPageURL) return;
                let divider = makeDivider(itemListSelector, () => {
                    let divider = document.createElement("tr");
                    let dividerContent = document.createElement("td");
                    divider.setAttribute("class", "tr2 spp-next-page-loader-divider")
                    divider.appendChild(dividerContent);
                    dividerContent.colSpan = 7;
                    dividerContent.style.textAlign = "center";
                    dividerContent.style.fontWeight = "bold";
                    dividerContent.innerText = "...";
                    return divider;
                });
                divider.firstChild.innerText = "正在获取下一页的帖子......";
                let p = nextPageLoader.GetURLDummy(nextPageURL);
                p
                    .then(html => {
                        nextPageLoader.nextPageDummy.innerHTML = html
                        if (GM_getValue("BlockSearchResultFromADForum")) BlockSearchResultFromADForum(nextPageLoader.nextPageDummy);
                    })
                    .catch(err => {
                        console.error(err);
                        divider.firstChild.innerText = "获取下一页的帖子出错,请手动刷新";
                    })
                    .finally(() => {
                        nextPageLoader.isFetching = false;
                        divider.firstChild.innerText = "滚动条到底后继续向下滚动将会加载下一页的帖子";
                    });

            }
            // 否则判断一下是否到底了,到底了就追加下一页的内容
            else if (Math.abs(document.documentElement.scrollHeight - (window.pageYOffset + window.innerHeight)) < 20) {
                let divider = getElementByXpath(document, "//tr[@class='tr2 spp-next-page-loader-divider'][last()]");
                nextPageLoader.AppendNextPageItems(itemListSelector, divider);
                nextPageLoader.UpdatePageList();

                divider.firstChild.innerText = `以下是第${nextPageURL.match(/page-(\d+)/)[1]}页`;
                window.history.pushState({}, 0, nextPageURL); // 将地址栏也改变了
                nextPageLoader.nextPageDummy = null;
            }

        })
    }
    // 处理主题列表页面
    if (pageType === PageType.THREADS_PAGE) {
        document.addEventListener('wheel', (e) => {
            e.stopPropagation();
            const itemListSelector = ".tr3.t_one";
            if (e.deltaY < 0 || nextPageLoader.isFetching) return;
            if (!nextPageLoader.nextPageDummy) {
                nextPageURL = getNextPageUrl();
                if (!nextPageURL) return;
                let divider = makeDivider(itemListSelector, () => {
                    let divider = document.createElement("tr");
                    let dividerContent = document.createElement("td");
                    divider.setAttribute("class", "tr2 spp-next-page-loader-divider")
                    divider.appendChild(dividerContent);
                    dividerContent.colSpan = 5;
                    dividerContent.style.textAlign = "center";
                    dividerContent.style.fontWeight = "bold";
                    dividerContent.innerText = "...";
                    return divider;
                });
                divider.firstChild.innerText = "正在获取下一页的帖子......";
                let p = nextPageLoader.GetURLDummy(nextPageURL);
                p
                    .then(html => {
                        nextPageLoader.nextPageDummy.innerHTML = html;
                        threadAddAnchorAttribute(nextPageLoader.nextPageDummy, page + 1, fid);
                        if (GM_getValue("highlightLastViewedThread")) highlightLastViewedThread(nextPageLoader.nextPageDummy);
                    })
                    .catch(err => {
                        console.error(err);
                        divider.firstChild.innerText = "获取下一页的帖子出错,请手动刷新";
                    })
                    .finally(() => {
                        nextPageLoader.isFetching = false;
                        divider.firstChild.innerText = "滚动条到底后继续向下滚动将会加载下一页的帖子";
                    });

            }
            // 否则判断一下是否到底了,到底了就追加下一页的内容
            else if (Math.abs(document.documentElement.scrollHeight - (window.pageYOffset + window.innerHeight)) < 20) {
                let divider = getElementByXpath(document, "//tr[@class='tr2 spp-next-page-loader-divider'][last()]");
                nextPageLoader.AppendNextPageItems(itemListSelector, divider);
                nextPageLoader.UpdatePageList();
                divider.firstChild.innerText = `以下是第${page + 1}页`;
                window.history.pushState({}, 0, nextPageURL); // 将地址栏也改变了
                page += 1;
                nextPageLoader.nextPageDummy = null;
            }

        })
    }
    // 处理楼层列表页面
    if (pageType === PageType.POSTS_PAGE) {
        document.addEventListener('wheel', (e) => {
            e.stopPropagation();
            const itemListSelector = ".t5.t2";
            if (e.deltaY < 0 || nextPageLoader.isFetching) return;
            if (!nextPageLoader.nextPageDummy) {
                nextPageURL = getNextPageUrl();
                if (!nextPageURL) return;
                let divider = makeDivider(itemListSelector, () => {
                    let divider = document.createElement("div");
                    let dividerContent = document.createElement("span");
                    divider.setAttribute("class", "t5 t2 spp-next-page-loader-divider")
                    divider.appendChild(dividerContent);
                    divider.style.textAlign = "center";
                    divider.style.fontWeight = "bold";
                    divider.style.fontSize = "14px";
                    divider.innerText = "...";
                    return divider;
                });
                divider.innerText = "加载中..";
                nextPageLoader.GetURLDummy(nextPageURL)
                    .then(html => {
                        nextPageLoader.nextPageDummy.innerHTML = html
                        postAddAnchorAttribute(nextPageLoader.nextPageDummy, page + 1, tid);
                        if (GM_getValue("loadingBoughtPostWithoutRefresh")) loadingBoughtPostWithoutRefresh(nextPageLoader.nextPageDummy);
                        if (GM_getValue("hidePostImage")) hidePostImage(nextPageLoader.nextPageDummy);
                        if (GM_getValue("hideUserAvatar")) hideUserAvatar(nextPageLoader.nextPageDummy);
                    })
                    .catch(err => {
                        console.error(err);
                        divider.innerText = "获取下一页的帖子出错,请手动刷新";
                    })
                    .finally(() => {
                        nextPageLoader.isFetching = false;
                        divider.innerText = "滚动条到底后继续向下滚动将会加载下一页的帖子";
                    });

            }
            // 否则判断一下是否到底了,到底了就追加下一页的内容
            else if (Math.abs(document.documentElement.scrollHeight - (window.pageYOffset + window.innerHeight)) < 20) {
                let divider = getElementByXpath(document, "//div[@class='t5 t2 spp-next-page-loader-divider'][last()]");
                nextPageLoader.AppendNextPageItems(itemListSelector, divider);
                nextPageLoader.UpdatePageList();
                divider.innerText = `以下是第${page + 1}页`;
                // window.history.pushState({}, 0, nextPageURL); // 将地址栏也改变了
                page += 1;
                nextPageLoader.nextPageDummy = null;
            }

        })
    }
    // 处理图墙区主题列表页面
    if (pageType === PageType.PIC_WALL_PAGE) {
        document.addEventListener('wheel', (e) => {
            e.stopPropagation();
            const itemListSelector = ".dcsns-li.dcsns-rss.dcsns-feed-0";
            if (e.deltaY < 0 || nextPageLoader.isFetching) return;
            if (!nextPageLoader.nextPageDummy) {
                nextPageURL = getNextPageUrl();
                if (!nextPageURL) return;
                let divider = makeDivider(itemListSelector, () => {
                    let divider = document.createElement("tr");
                    let dividerContent = document.createElement("td");
                    divider.setAttribute("class", "tr2 spp-next-page-loader-divider")
                    divider.appendChild(dividerContent);
                    dividerContent.colSpan = 5;
                    dividerContent.style.textAlign = "center";
                    dividerContent.style.fontWeight = "bold";
                    dividerContent.innerText = "...";
                    return divider;
                });
                divider.firstChild.innerText = "正在获取下一页的帖子......";
                let p = nextPageLoader.GetURLDummy(nextPageURL);
                p
                    .then(html => {
                        nextPageLoader.nextPageDummy.innerHTML = html;
                        nextPageLoader.nextPageDummy.querySelectorAll(".dcsns-li.dcsns-rss.dcsns-feed-0 .lazy").forEach(ele => {
                            ele.setAttribute("loading", "lazy");
                            ele.setAttribute("class", "");
                            ele.setAttribute("src", ele.getAttribute("data-original"));
                            ele.setAttribute("data-original", "");
                            ele.style.display = "inline";
                        });

                    })
                    .catch(err => {
                        console.error(err);
                        divider.firstChild.innerText = "获取下一页的帖子出错,请手动刷新";
                    })
                    .finally(() => {
                        nextPageLoader.isFetching = false;
                        divider.firstChild.innerText = "滚动条到底后继续向下滚动将会加载下一页的帖子";
                    });

            }
            // 否则判断一下是否到底了,到底了就追加下一页的内容
            else if (Math.abs(document.documentElement.scrollHeight - (window.pageYOffset + window.innerHeight)) < 20) {
                let divider = getElementByXpath(document, "//tr[@class='tr2 spp-next-page-loader-divider'][last()]");
                nextPageLoader.AppendNextPageItems(itemListSelector, divider);
                nextPageLoader.UpdatePageList();
                divider.firstChild.innerText = `以下是第${page + 1}页`;
                window.history.pushState({}, 0, nextPageURL); // 将地址栏也改变了
                page += 1;
                nextPageLoader.nextPageDummy = null;
            }

        })
    }
}

async function automaticTaskCollection() {

    function setUIDsValue(uid, value) {
        let tmp = GM_getValue("LastAutomaticTaskCollectionDate") || {};
        tmp[uid] = value;
        GM_setValue("LastAutomaticTaskCollectionDate", tmp);
    }

    if (document.querySelector("#login_0")) {
        console.log(`尚未登录,不接任务`);
        return
    }

    let uid = document.querySelector("#menu_profile .ul2").innerHTML.match(/u\.php\?action-show-uid-(\d+)\.html/)[1];
    let uname = document.querySelector("#user-login a").innerText;

    console.log(GM_getValue("LastAutomaticTaskCollectionDate"));
    let lastTime = GM_getValue("LastAutomaticTaskCollectionDate") ?
        (parseInt(GM_getValue("LastAutomaticTaskCollectionDate")[uid]) || 0) : 0;
    console.log(`${uname}[${uid}] 上次:${new Date(lastTime).toLocaleDateString()} ${new Date(lastTime).toLocaleTimeString()}`);


    if (new Date().getTime() - lastTime < (3600 * 1000)) {
        console.log("再等等……");
        return;
    }

    let sleep = (ms) => {
        return new Promise(resolve => setTimeout(resolve, ms))
    }

    async function forumTask(pageURL, selector, jobType) {
        let dummy = await fetch(
            pageURL,
            {credentials: 'include', mode: "no-cors"})
            .then(response => response.text())
            .then(html => {
                let dummy = document.createElement("html");
                dummy.innerHTML = html;
                return dummy
            })
            .catch(err => console.error(err));

        async function t(task) {
            let job = task.getAttribute("onclick");
            let r = job.match(/startjob\('(\d+)'\);/);
            let jobID = r[1];
            let taskURL = `/plugin.php?H_name=tasks&action=ajax&actions=${jobType}&cid=${jobID}&nowtime=${new Date().getTime()}&verify=${verifyhash}`;


            await fetch(taskURL, {credentials: 'include', mode: "no-cors"})
                .then(response => response.text())
                .then(html => {
                        console.log(html);
                        if (html.includes("success\t")) alert(html.match(/!\[CDATA\[success\t(.+)]]>/)[1]);
                    }
                )
                .catch(err => console.error(err));
        }

        await dummy.querySelectorAll(selector).forEach(t);

        console.log(`${pageURL} done, ${new Date().getTime()}`)
    }

    for (let i = 0; i < 2; i++) {
        forumTask(
            "/plugin.php?H_name-tasks.html",
            "a[title=按这申请此任务]",
            "job"
        ).catch(err => console.error(err));
        await sleep(3000);
        forumTask(
            "/plugin.php?H_name-tasks-actions-newtasks.html.html",
            "a[title=领取此奖励]",
            "job2"
        ).catch(err => console.error(err));
    }
    console.log(`${uname}[${uid}], 本次领取时间:${new Date().getTime()}`);
    setUIDsValue(uid, new Date().getTime());

}

function BlockSearchResultFromADForum(target = document) {
    target.querySelectorAll(".tr3.tac").forEach(ele => {
        let forum = ele.childNodes[2];
        if (forum.firstChild.getAttribute("href").match(/fid-17[1-4]/)) {
            ele.style.display = "none";
        }
    });
}

function BackToTop() {

    function isInViewPort(ele) {
        const viewWidth = window.innerWidth;
        const viewHeight = window.innerHeight;
        const {
            top,
            right,
            bottom,
            left,
        } = ele.getBoundingClientRect();

        return (
            top >= 0
            && left >= 0
            && right <= viewWidth
            && bottom <= viewHeight
        );
    }

    let backToTop = document.createElement("div");

    backToTop.innerHTML = "<button>回到顶部</button>";
    backToTop.setAttribute("id", "spp-back-to-top");
    backToTop.setAttribute("draggable", "true");
    backToTop.style.display = "block";
    backToTop.style.position = "fixed";
    backToTop.style.background = "#efefef";
    backToTop.style.left = "calc(50vw + 470px)";
    backToTop.style.bottom = "40px";
    backToTop.style.left = GM_getValue("backToTop_left") ? GM_getValue("backToTop_left") : "calc(50vw + 470px)";
    backToTop.style.bottom = GM_getValue("backToTop_bottom") ? GM_getValue("backToTop_bottom") : "40px";
    backToTop.style.zIndex = "99";
    backToTop.style.width = "30px";
    backToTop.style.padding = "10";
    backToTop.style.borderRadius = "5px";
    let main = document.getElementById("main");
    main.appendChild(backToTop);
    backToTop.addEventListener("click", (e) => {
        e.stopPropagation();
        window.scrollTo({top: 0, behavior: "smooth"});
    });
    let offsetX;
    let offsetY;

    backToTop.addEventListener("dragstart", (e) => {
        e.stopPropagation();
        // console.log(`e.offset:${e.offsetX},${e.offsetY}`);
        // 得到鼠标在元素内的偏移
        offsetX = e.offsetX;
        offsetY = e.offsetY;
    });

    backToTop.addEventListener("dragend", (e) => {
        e.stopPropagation();
        // alert("?");
        console.log(`e.client:${e.clientX},${e.clientY}`);
        // 获得丄的交叉点坐标
        let _50vw = window.innerWidth / 2;
        let _100vh = window.innerHeight;

        // 计算出相对丄交叉点的偏移量
        let offsetLeft = e.clientX - offsetX - _50vw;
        let offsetBottom = _100vh - e.clientY - (e.target.clientHeight - offsetY);

        // 防止拖到视口以外了
        if (_50vw + offsetLeft >= 0
            && _50vw + offsetLeft + e.target.clientWidth <= window.innerWidth
            && offsetBottom > 0
            && offsetBottom + e.target.clientHeight <= window.innerHeight
        ) {
            backToTop.style.left = `calc(50vw + ${offsetLeft}px)`;
            backToTop.style.bottom = `${offsetBottom}px`;

            GM_setValue("backToTop_left", backToTop.style.left);
            GM_setValue("backToTop_bottom", backToTop.style.bottom);
        }

    });


}

function postAddAnchorAttribute(target, pg, threadID) {
    target.querySelectorAll(".tpc_content .f14").forEach(ele => {
        ele.setAttribute("page", pg);
        ele.setAttribute("tid", threadID);
        let pid = ele.previousElementSibling.getAttribute("name");
        ele.setAttribute("pid", pid);

    });
}

function threadAddAnchorAttribute(target, pg, forumID) {
    target.querySelectorAll(".tr3.t_one").forEach(ele => {
        ele.setAttribute("page", pg);
        ele.setAttribute("fid", forumID);
        let tid_m = ele.querySelector("a").getAttribute("href").match(/tid-(\d+)/);
        if (!tid_m) return;
        let tid = tid_m[1];
        ele.setAttribute("tid", tid);

    });
}

function highlightLastViewedThread(target = document) {
    target.querySelectorAll("a").forEach(ele => {
         
        let threadContainer = ele.closest(".tr3.t_one");
        if (!threadContainer) return;
        let tid = threadContainer.getAttribute("tid");

        function recordTid(event) {
            event.stopPropagation();
            sessionStorage.setItem("lastViewedThread", tid);
        }

        ele.addEventListener("click", recordTid);
        // ele.addEventListener("dragend", recordTid);
        console.log(`上次浏览的帖子tid:${sessionStorage.getItem("lastViewedThread")}`);
        if (ele.getAttribute("id") === `a_ajax_${sessionStorage.getItem("lastViewedThread")}`) {
            ele.parentNode.parentNode.parentNode.style.backgroundColor = "#deeeff";

            document.addEventListener('readystatechange', (event) => {
                if (document.readyState === "complete") {

                    history.scrollRestoration = "manual";
                    ele.scrollIntoView({behavior: "auto", block: "center"});

                }
            });
        }
    });
}

//##############################################################
// 执行入口
//##############################################################
(function () {


    registerAllOptions();

    let mainInterval = setInterval(main, 50);
    let mainStart = (new Date()).getTime();

    function main() {

        // 避免有些外链图一直加载不完,在那转圈圈,让脚本一直无法生效
        document.querySelectorAll("img").forEach(e => e.setAttribute("loading", "lazy"));

        if ((new Date()).getTime() - mainStart > 20000 || document.readyState === "interactive"
            || document.readyState === "complete") {
            clearInterval(mainInterval);
        } else {
            return
        }

        //##############################################################
        // 调试代码
        //##############################################################
        // GM_listValues().forEach(vName => {console.log(vName);GM_deleteValue(vName);});
        // console.log(GM_listValues());
        // return;
        //##############################################################
        BackToTop();

        if (document.location.href.includes("/read.php")) {
            postAddAnchorAttribute(document, page, tid);
        }
        if (document.location.href.includes("/thread.php")) {
            threadAddAnchorAttribute(document, page, fid);
        }
        if (GM_getValue("loadingBoughtPostWithoutRefresh") && document.location.href.includes("/read.php")) {
            loadingBoughtPostWithoutRefresh();
        }
        if (GM_getValue("hidePostImage") && document.location.href.includes("/read.php")) {
            hidePostImage();
        }
        if (GM_getValue("hideUserAvatar") && document.location.href.includes("/read.php")) {
            hideUserAvatar();
        }
        if (GM_getValue("dynamicLoadingThreads") && (document.location.href.includes("/thread.php"))) {
            dynamicLoadingNextPage(PageType.THREADS_PAGE);
        }
        if (GM_getValue("dynamicLoadingPicWall") && document.location.href.includes("/thread_new.php")) {
            dynamicLoadingNextPage(PageType.PIC_WALL_PAGE);
        }
        if (GM_getValue("dynamicLoadingPosts") && document.location.href.includes("/read.php")) {
            dynamicLoadingNextPage(PageType.POSTS_PAGE);
        }
        if (GM_getValue("dynamicLoadingSearchResult") && document.location.href.includes("/search.php")) {
            dynamicLoadingNextPage(PageType.SEARCH_RESULT);
        }
        if (GM_getValue("BlockSearchResultFromADForum") && document.location.href.includes("/search.php")) {
            BlockSearchResultFromADForum();
        }
        if (GM_getValue("automaticTaskCollection")) {
            automaticTaskCollection();
        }
        if (GM_getValue("highlightLastViewedThread") && (document.location.href.includes("/thread.php") || document.location.href.includes("/thread_new.php"))) {
            highlightLastViewedThread();
        }

    }
})()