Soul++

提升你的魂+使用体验

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name            Soul++
// @namespace       SoulPlusPlus
// @version         1.0.2
// @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
// ----------------COPY START---------------------
// @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://*.east-plus.net/*
// --------------------------------------------
// @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://east-plus.net/*
// --------------------------------------------
// @grant           GM_getValue
// @grant           GM_setValue
// @grant           GM_listValues
// @grant           GM_addValueChangeListener
// @grant           GM_removeValueChangeListener
// @grant           GM_notification
// @grant           GM_deleteValue
// @grant           GM_addStyle
// @grant           GM_getResourceText
// @grant           unsafeWindow
// --------------------------------------------
// @require         https://cdn.jsdelivr.net/npm/[email protected]/src/toastify.min.js
// @resource        TOASTIFY_CSS https://cdn.jsdelivr.net/npm/[email protected]/src/toastify.css
// ----------------COPY END---------------------
// @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 ToastType = Object.freeze({
    INFO: Symbol("信息"),
    SUCCESS: Symbol("成功"),
    DANGER: Symbol("危险,失败"),
    WARNING: Symbol("警告")
});

const FETCH_CONFIG = {
    credentials: 'include',
    mode: "no-cors"
};

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

function waitForImageToLoad(imageElement) {
    return new Promise(resolve => {
        imageElement.onload = resolve
    })
}

function toast(info, toastType, time = 3000, close = true) {
    let t;
    switch (toastType) {
        case ToastType.INFO:
            t = "linear-gradient(109deg, #3da1e0, #004dc1)";
            break;
        case ToastType.SUCCESS:
            t = "linear-gradient(213deg, #5daa16, #05bb1b)";
            break;
        case ToastType.DANGER:
            t = "linear-gradient(18deg, #cb3131, #ac1415)";
            break;
        case ToastType.WARNING:
            t = "linear-gradient(180deg, #e98202, #fe5e00)";
            break;
        default:
            t = "linear-gradient(109deg, #3da1e0, #004dc1)";
    }
    Toastify({
        text: info,
        duration: time,
        close: close,
        gravity: "bottom", // `top` or `bottom`
        position: "right", // `left`, `center` or `right`
        stopOnFocus: true, // Prevents dismissing of toast on hover
        style: {
            background: t,
        },
        onClick: function () {
        } // Callback after click
    }).showToast();

}

function getTimeStamp() {
    return new Date().getTime();
}

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


function getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

async function fetchRetry(url, options, n = 1) {
    try {
        return await fetch(url, options)
    } catch (err) {
        if (n <= 1) throw err;
        return await fetchRetry(url, options, n - 1);
    }
}

async function getPage(url, dummy = false, retry = 3, toastPop = false) {
    return await fetchRetry(url, FETCH_CONFIG, retry)
        .then(resp => resp.text())
        .then(html => {
            if (dummy) {
                let dummy = document.createElement("html");
                dummy.insertAdjacentHTML('afterbegin', html);
                return dummy
            } else {
                return html
            }
        }).catch(e => {
                if (toastPop) {
                    toast(`访问 ${url} 失败\n${e}`, ToastType.WARNING);
                } else {
                    console.error(`访问 ${url} 失败\n${e}`);
                }
            }
        );
}

class GMK {

    static addStyle(css) {
        return GM_addStyle(css);
    }

    static setValue(key, value) {
        return GM_setValue(key, value)
    }

    static getValue(key) {
        return GM_getValue(key)
    }

    static getResourceText(key) {
        return GM_getResourceText(key);
    }

    static listValues() {
        return GM_listValues();
    }

    static deleteValue(_name) {
        return GM_deleteValue(_name);
    }

    // listener_id = GM_addValueChangeListener(name, function(name, old_value, new_value, remote) {})
    static addValueChangeListener(_name, callback) {
        return GM_addValueChangeListener(_name, callback);
    }

    static removeValueChangeListener(listener_id) {
        return GM_removeValueChangeListener(listener_id);
    }
}

class MppManager {
    static TASK_KEY = "Soul++:MppThreadsStatus";

    constructor() {
    }

    static isThreadExist(_tid) {
        return this.getMarkList().hasOwnProperty(_tid);
    }

    static getMarkList() {
        return GMK.getValue(this.TASK_KEY) || {}
    }

    static addMarkThread(_tid) {
        GMK.setValue(this.TASK_KEY, { ...this.getMarkList(), ...{ [_tid]: {} } })
    }

    static deleteMarkedThread(_tid) {
        let markList = this.getMarkList();
        delete markList[_tid];
        GMK.setValue(this.TASK_KEY, markList)
    }

    static isMarked(_tid) {
        return this.getMarkList().hasOwnProperty(_tid)
    }

    static getAllThreadStatus() {
        return this.getMarkList();
    }

    static setThreadStatus(_tid, threadStatus) {
        GMK.setValue(this.TASK_KEY, {
            ...this.getMarkList(),
            [_tid]: threadStatus
        });
    }

    static getLastFetchTime(_tid) {
        let res = Object.entries(GMK.getValue(this.TASK_KEY)).filter(e => e[0] === _tid);
        return res[0][1]["lastFetchTime"];
    };

    static isAllChecked() {
        let res = Object.entries(GMK.getValue(this.TASK_KEY)).filter(e => !e[1]["allPagesChecked"]);
        return res.length === 0;
    };
}

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

function buyRefresh_free(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, FETCH_CONFIG)
                    .then(resp => resp.text())
                    .then(text => {
                        if (!text.includes("操作完成")) {
                            toast("购买失败!", ToastType.DANGER);
                            return;
                        }
                        let threadID = postContainer.getAttribute("tid");
                        let pg = postContainer.getAttribute("page");
                        let resultURL = `./read.php?tid=${threadID}&page=${pg}`;
                        fetch(resultURL, FETCH_CONFIG).then(resp => resp.text())
                            .then(html => {
                                let dummy = document.createElement("html");
                                dummy.innerHTML = html;
                                if (GMK.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) {
                toast(`发送请求出错,购买失败!\n${error}`, ToastType.DANGER);
                console.log('Request Failed', error);
            }
        })

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

function hideImg(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;

    // 如果开启了按需加载
    if (GMK.getValue("loadImageOnDemand")) {
        img.dataset.src = img.getAttribute("src");
        img.setAttribute("src", "")
    }

    // 如果图片的父元素是A标签,去掉它
    if (img.parentNode.tagName === "A") img.parentNode.replaceWith(img);
    // 创建包裹元素
    let wrapper = document.createElement('div');
    wrapper.setAttribute("class", "spp-img-mask");
    wrapper.style.display = "grid";
    wrapper.style.gridTemplateRows = "auto auto";
    wrapper.style.justifyItems = "center";

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

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

    img.style.width = "100%";

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

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

    // 创建遮罩小人儿表情
    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");


    // 创建遮罩文本
    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 spp-hide");
    tip.setAttribute("class", "ssp-img-mask-text");

    // 插入元素
    wrapper.insertBefore(tip, img);
    // 防止点击图片打开新窗口
    document.querySelector(".spp-thread-imgs").addEventListener("click", e => e.preventDefault());
    // 事件监听
    wrapper.addEventListener("click", (e) => {
        e.stopPropagation();
        // console.log(e.target);
        // console.log(e.currentTarget);
        let img = e.currentTarget.querySelector(".spp-thread-imgs");
        img.classList.toggle("spp-hide");
        // 按需加载
        if (GMK.getValue("loadImageOnDemand") && !img.classList.contains("spp-hide")) {
            let loading = document.createElement("div");
            loading.innerHTML = `<div class="spp-loading-animation">
                <div class="dot1"></div>
                <div class="dot2"></div>
                <div class="dot3"></div>
            </div>`;
            loading = loading.firstChild;
            img.parentNode.append(loading);
            img.setAttribute("src", img.dataset.src);
            waitForImageToLoad(img).then(() => {
                loading.parentNode.removeChild(loading);
            });
        }
        e.currentTarget.querySelector(".spp-img-mask-icon-hide").classList.toggle("spp-hide");
        e.currentTarget.querySelector(".spp-img-mask-icon-show").classList.toggle("spp-hide");
    });

}

function hideAvatar(avatar) {
    let src = avatar.getAttribute("src");
    if (src === "images/face/none.gif") return;

    // 如果开启了按需加载
    if (GMK.getValue("loadImageOnDemand")) {
        avatar.dataset.src = avatar.getAttribute("src");
        avatar.setAttribute("src", "")
    }

    // 创建包裹元素
    let wrapper = document.createElement('div');
    wrapper.setAttribute("class", "spp-avatar-mask");
    wrapper.style.minWidth = "162px";
    wrapper.style.minHeight = "162px";
    wrapper.style.display = "grid";
    wrapper.style.justifyItems = "center";
    wrapper.style.alignItems = "center";

    // 创建一个假头像
    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.classList.add("spp-hide");

    // 设置类名
    avatar.classList.add("spp-avatar-real");
    fakeAvatarElement.classList.add("spp-avatar-fake");

    // 事件监听
    wrapper.addEventListener("mouseenter", (e) => {
        e.stopPropagation();
        e.currentTarget.querySelector(".spp-avatar-fake").classList.add("spp-hide");
        e.currentTarget.querySelector(".spp-avatar-real").classList.remove("spp-hide");
        // 按需加载
        if (GMK.getValue("loadImageOnDemand") && !avatar.classList.contains("spp-hide")) {
            let loading = document.createElement("div");
            loading.innerHTML = `<div class="spp-loading-animation">
                <div class="dot1"></div>
                <div class="dot2"></div>
                <div class="dot3"></div>
            </div>`;
            loading = loading.firstChild;
            e.currentTarget.append(loading);
            avatar.setAttribute("src", avatar.dataset.src);
            waitForImageToLoad(avatar).then(() => {
                loading.parentNode.removeChild(loading);
            });
        }
    });

    wrapper.addEventListener("mouseleave", (e) => {
        e.stopPropagation();
        e.currentTarget.querySelector(".spp-avatar-fake").classList.remove("spp-hide");
        e.currentTarget.querySelector(".spp-avatar-real").classList.add("spp-hide");
        e.currentTarget.querySelectorAll(".spp-loading-animation").forEach(ele => ele.parentNode.removeChild(ele));
    });


}

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

    thread_user_post_images.forEach(hideImg);
}

function hideUserAvatar(target = document) {
    let user_avatars = target.querySelectorAll(".user-pic img");
    user_avatars.forEach(hideAvatar);
}

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, FETCH_CONFIG)
                .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 pageSeq = document.querySelector(".pages b");
        if (!pageSeq) return null;
        let pageNum = pageSeq.parentNode;
        let url = pageNum.nextSibling.firstChild.getAttribute("href");
        if (pageNum.nextSibling.nextSibling.classList.contains("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 (GMK.getValue("blockAdforumSearchResult")) blockAdforumSearchResult(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 (GMK.getValue("highlightViewedThread")) highlightViewedThread(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 (GMK.getValue("buyRefresh_free")) buyRefresh_free(nextPageLoader.nextPageDummy);
                        if (GMK.getValue("hidePostImage")) hidePostImage(nextPageLoader.nextPageDummy);
                        if (GMK.getValue("hideUserAvatar")) hideUserAvatar(nextPageLoader.nextPageDummy);
                        if (GMK.getValue("hoistingResourcePost")) hoistingResourcePost(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 = GMK.getValue("LastAutomaticTaskCollectionDate") || {};
        tmp[uid] = value;
        GMK.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(GMK.getValue("LastAutomaticTaskCollectionDate"));
    let lastTime = GMK.getValue("LastAutomaticTaskCollectionDate") ?
        (parseInt(GMK.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;
    }


    async function forumTask(pageURL, selector, jobType) {
        let dummy = await fetch(
            pageURL,
            FETCH_CONFIG)
            .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, FETCH_CONFIG)
                .then(response => response.text())
                .then(html => {
                        console.log(html);
                        if (html.includes("success\t")) toast(html.match(/!\[CDATA\[success\t(.+)]]>/)[1], ToastType.SUCCESS);
                    }
                )
                .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 blockAdforumSearchResult(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 createFloatDraggableButton(text, GMKey, style) {
    let btn = document.createElement("button");
    let main = document.getElementById("main");
    main.appendChild(btn);

    btn.innerText = text;
    btn.setAttribute("id", "spp-float-draggable-button");
    btn.setAttribute("draggable", "true");
    btn.style.display = "block";
    btn.style.position = "fixed";
    btn.style.background = "#efefef";
    btn.style.zIndex = "99";
    btn.style.width = "30px";
    btn.style.padding = "10";
    btn.style.borderRadius = "1px";

    if (style) {
        for (const k in style) {
            btn.style[k] = style[k];
        }
    }

    let GM_style;
    if (GMKey) GM_style = GMK.getValue(GMKey);
    if (GM_style) {
        for (const k in GM_style) {
            btn.style[k] = GM_style[k];
        }
    }

    return btn;

}

function mark() {
    if (document.location.href.includes("/read.php")) {

        const GREY = "linear-gradient(to top, rgb(184 184 184), rgb(188 188 188))";
        const BLACK = "linear-gradient(to top, #313131,#000000)";
        const GMKey = "Style_markPlusPlus";

        let markButton = createFloatDraggableButton(
            MppManager.isMarked(tid) ? "MARKED" : "MARK",
            GMKey,
            {
                left: "calc(50vw + 470px)",
                top: "234px",
                background: MppManager.isMarked(tid) ? GREY : BLACK,
                color: "white",
                fontWeight: "bold",
                outline: "none",
                border: "none",
                borderRadius: "3px",
                width: "30px",
                opacity: MppManager.isMarked(tid) ? "0.4" : "0.8",
                cursor: "pointer",
            },
        );

        let dragStart = {};
        let dragEnd = {};
        // 防止拖到视口以外了
        AddIntersectionObserver(([entry]) => {
            if (!entry.isIntersecting) {
                // console.log('LEAVE');
                markButton.style.left = dragStart['saved']['left'];
                markButton.style.top = dragStart['saved']['top'];
            }
        }, markButton)

        markButton.addEventListener("click", async evt => {
            evt.stopPropagation();

            if (MppManager.isMarked(tid)) {
                MppManager.deleteMarkedThread(tid);
                evt.target.style.background = BLACK;
                evt.target.innerText = "MARK";
                evt.target.style.opacity = "0.8";
            } else {
                MppManager.addMarkThread(tid);
                evt.target.style.background = GREY;
                evt.target.innerText = "MARKED";
                evt.target.style.opacity = "0.4";
                // 第一次mark就先把基本内容给收录了
                let threadStatus = {};

                threadStatus['page'] = 1;
                threadStatus['lastFetchTime'] = 0;
                threadStatus['maxPage'] = totalpage;
                threadStatus['title'] = document.querySelector('.crumbs-item.current strong>a').textContent;
                threadStatus["markTime"] = new Date().toLocaleDateString();
                MppManager.setThreadStatus(tid, threadStatus);
                console.log(MppManager.getMarkList());
            }


        });

        markButton.addEventListener("contextmenu", openStatus);

        markButton.addEventListener("dragstart", (e) => {
            e.stopPropagation();
            dragStart = {
                clientX: e.clientX,
                clientY: e.clientY,
                saved: {
                    left: e.target.style.left,
                    top: e.target.style.top,
                }
            }
        });

        markButton.addEventListener("dragend", (e) => {
            e.stopPropagation();
            // 获得丅的交叉点坐标
            let startX = window.innerWidth / 2;
            let startY = 0;
            dragEnd = {
                clientX: e.clientX,
                clientY: e.clientY,
            }
            let newLeft = parseFloat(e.target.style.left.match(/(-?\d+)px/)[1]) + (dragEnd.clientX - dragStart.clientX)
            let newTop = parseFloat(e.target.style.top.match(/(-?\d+)px/)[1]) + (dragEnd.clientY - dragStart.clientY);


            e.target.style.left = `calc(50vw + ${newLeft}px)`;
            e.target.style.top = `${newTop}px`;

            let tmp = {
                ...GMK.getValue(GMKey),
                ...{
                    left: `calc(50vw + ${newLeft}px)`,
                    top: `${newTop}px`,
                }
            };
            GMK.setValue(GMKey, tmp);
        });
    }
    let menuButton = document.createElement("li");
    let a = document.createElement("a");
    a.innerText = "我的MARK";
    a.style.cursor = "pointer";
    a.classList.add("mpp-status");
    menuButton.appendChild(a);
    document.querySelector("#main").insertAdjacentHTML("afterbegin", `
   <style>
        .mpp{
            position: fixed;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            background: white;
            z-index: 2000000;
        }
        .mpp-mask{
            position: fixed;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            background: black;
            opacity: 0.5;
            user-select: none;
            z-index: 1000000;
        }
        .mpp-container{
            background: #eeeeee;
            display:grid;
            grid-template-areas:
            "title"
            "main";
            grid-template-rows: 28px auto;
            grid-gap: 10px;
            min-height: 80vh;
            max-height: 80vh;
            width: 900px;
            overflow-y: hidden;
        }
        .mpp-title{
            grid-area: title;
            background: #111111;
            border-left: black;
            border-right: black;
            text-align: center;
            font-weight: bold;
            height: 100%;
            color: white;
            padding-top: 5px;
            margin: 0;
        }

        .mpp-main{
            grid-area: main;
            height: 75vh;
            overflow-y: scroll;
            overflow-x: hidden;

        }


        .spp-hide{
            display: none;
        }

        .mpp-accordion{
            width: 100%; 
            border: none;
            outline: none;
            background-color: whitesmoke;
            text-align: left;
            padding: 10px 10px;
            font-size: 12px;
            /*font-weight: bold;*/
            color: #444;
            cursor: pointer;
            transition: background-color 0.2s linear;
            display: inline-grid;
            grid-template-columns:1fr 4fr 1fr 1fr 1fr 1fr 1fr;
            align-items: center;
            justify-items: center;
            margin-bottom: 4px;
            box-shadow: 1px 2px 2px #AAAAAA;
            
        }
        .mpp-header{
            width: 100%;
            display: inline-grid;
            grid-template-columns:1fr 4fr 1fr 1fr 1fr 1fr 1fr; 
            padding: 10px 10px;
            font-size: 12px;
            align-items: center;
            justify-items: left;
        }

        /*.mpp-accordion-plus{ */
        /*    font-size: 14px;*/
        /*    float: right;*/
        /*}*/

        button.mpp-accordion:before{
            content: '无';
            font-size: 10px;
            color: gray;
        }
        button.mpp-accordion.have-content:before{
            content: '+';
            font-size: 14px;
            font-weight: bold;
            color: black;
        }
        button.mpp-accordion.have-content.mpp-accordion-is-open:before{
            content: '-';
            font-size: 14px;
            font-weight: bold;
            color: black;
        }
        button.mpp-accordion:hover, button.mpp-accordion.mpp-accordion-is-open{
            background-color: #ddd;
        }

        .mpp-accordion-content{
            background: #eeeeee;
            border-left: 1px solid whitesmoke;
            border-right: 1px solid whitesmoke;
            padding: 0 20px;
            margin-bottom: 1px;
            max-height: 0;
            overflow: hidden;
            font-size: 10px;
        }

        .mpp-accordion-content.mpp-accordion-is-open{
            max-height: fit-content;
        }

        .mpp-sticky{
            position: sticky;
            top: 0;
        }

        .mpp-accordion-op{
            display: flex;

            justify-content: end;
            align-content: center;
        }
        .mpp-accordion-op a{
            padding: 5px;
            margin-left: 20px;
            font-size: 12px;
            cursor: pointer;
        }
        a.mpp-delete{
            text-align: right;
            color: brown;
        }
        
        a.mpp-sell{
            color: blueviolet;
            font-weight: bold;
        }
        
        a.mpp-hyperlink{
            color: blue;
        }
        a.mpp-hash{
            color: forestgreen;
        }
        
        .mpp-content-cell.mpp-content-title,
        .mpp-content-cell.mpp-content-last-fetch-time{
            justify-self: left; 
        }
        .mpp-content-cell.mpp-content-last-fetch-time{
            padding-left: 1em;
        }
        a.mpp-status{
            /*font-weight: bold;*/
            color: dodgerblue;
            cursor:pointer;
        }
        span.mpp-content-result{
            justify-self: center;
        }

    </style> 
    <div class="mpp-mask spp-hide"></div> 
    <div class="mpp spp-hide">
            <div class="mpp-container">
                <p class="mpp-title">我的Mark(保持此窗口开启才会运行)</p>
                <div class="mpp-main">
                    <div class="mpp-accordion-op mpp-sticky">
                        <a class="mpp-accordion-expand-all">全部展开</a>
                        <a class="mpp-accordion-collapse-all">全部折叠</a>  
                    </div>
                    <div class="mpp-header" >
                        <span class="mpp-header-cell mpp-content-result"></span> 
                        <span class="mpp-header-cell mpp-content-title" >帖子标题</span> 
                        <span class="mpp-header-cell">页数</span> 
                        <span class="mpp-header-cell mpp-content-last-fetch-time">检查时间</span> 
                        <span class="mpp-header-cell">悬赏状态</span> 
                        <span class="mpp-header-cell">MARK时间</span> 
                        <a class="mpp-delete mpp-content-cell" data-tid="1274464"></a>
                    </div>
                    <div class="mpp-content-container">
                        
                    </div>
                </div> 
            </div> 
        </div>
    
`);
    document.querySelector("#guide").prepend(menuButton);
    // document.querySelector('.fl>.gray2>.fl:first-child').insertAdjacentText("beforeend",
    //     `, `);
    // document.querySelector('.fl>.gray2>.fl:first-child').insertAdjacentHTML("beforeend",
    //     `<a class="mpp-status">我的MARK</a>`);
    document.querySelector(".mpp-accordion-expand-all").addEventListener("click", evt => {

        document.querySelectorAll(".mpp-accordion").forEach(ele => {
            if (!ele.nextElementSibling.querySelectorAll("p>a").length) return;
            if (!ele.classList.contains(" mpp-accordion-is-open")) ele.classList.add("mpp-accordion-is-open");
        });
        document.querySelectorAll(".mpp-accordion-content").forEach(ele => {
            if (!ele.querySelectorAll("p>a").length) return;
            if (!ele.classList.contains("mpp-accordion-is-open")) ele.classList.add("mpp-accordion-is-open");
            ele.style.maxHeight = ele.scrollHeight + 'px';
        });
    });
    document.querySelector(".mpp-accordion-collapse-all").addEventListener("click", evt => {
        document.querySelectorAll(".mpp-accordion").forEach(ele => {
            if (ele.classList.contains("mpp-accordion-is-open")) ele.classList.remove("mpp-accordion-is-open")
        });
        document.querySelectorAll(".mpp-accordion-content").forEach(ele => {
            if (ele.classList.contains("mpp-accordion-is-open")) ele.classList.remove("mpp-accordion-is-open")
            ele.style.maxHeight = null;
        });
    });

    // 用于tab之间广播通讯,只允许一个tab运行mark++
    const bc = new BroadcastChannel("Soul++:MppTaskStart");


    let refreshID;


    // 自己不会接到
    bc.onmessage = async msg => {
        console.log('BroadcastChannel:', msg.data);
        if (msg.data.includes("mppTaskStart")) {
            closeMenu(null);
            // toast("由于你在别的标签打开了“我的MARK”,此标签的“我的MARK”被关闭了",ToastType.WARNING, 99999 * 1000);
        }
    };


    function insertDataHTML() {
        let container = document.querySelector(".mpp-content-container");
        let threadsStatus = MppManager.getAllThreadStatus()
        let insertHTML = ``;
        for (const [_tid, status] of Object.entries(threadsStatus)) {
            let posts = "";
            if (status['sell']) status["sell"].forEach(ele => posts += `<p><a class="mpp-sell" href="${ele}" target="_blank">[出售]${ele}</a></p>`);
            if (status['hyperlink']) status["hyperlink"].forEach(ele => posts += `<p><a class="mpp-hyperlink" href="${ele}" target="_blank">[超链]${ele}</a></p>`);
            if (status["magnetOrMiaochuan"]) status["magnetOrMiaochuan"].forEach(ele => posts += `<p><a class="mpp-hash" href="${ele}" target="_blank">[磁力或秒传]${ele}</a></p>`);
            let button = container.querySelector(`button.mpp-accordion[data-tid="${_tid}"`);
            if (button) button.classList.remove("have-content");
            let content = container.querySelector(`div.mpp-accordion-content[data-tid="${_tid}"`);
            // console.log(button ? button.classList.toString() : "mpp-accordion");
            // console.log(content ? content.classList.toString() : "mpp-accordion-content");
            // <span class="mpp-content-cell mpp-accordion-plus">${posts === "" ? "" : button.classList.contains("") ? "-" : "+"}</span>
            insertHTML += `
            <button type="button" class="${button ? button.classList.toString() : "mpp-accordion"} ${posts ? "have-content" : ""}" data-tid="${_tid}">
                <a 
                class="mpp-content-cell mpp-content-title"  
                href="/read.php?tid=${_tid}" 
                target="_blank"
                >${status["title"].slice(0, 20)}${status["title"].length > 20 ? "..." : ""}</a> 
                <span class="mpp-content-cell">${status["page"] || 0} / ${status["maxPage"]}</span> 
                <span class="mpp-content-cell mpp-content-last-fetch-time">${status["lastFetchTime"] ? `${Math.round((getTimeStamp() - parseInt(status["lastFetchTime"])) / 1000 / 60)} 分钟之前` : '尚未检查'}</span> 
                <span class="mpp-content-cell">${status["offerState"]}</span> 
                <span class="mpp-content-cell">${status["markTime"]}</span> 
                <a class="mpp-delete mpp-content-cell" data-tid="${_tid}">删除</a>
            </button>

            <div class="${content ? content.classList.toString() : "mpp-accordion-content"}" data-tid="${_tid}">
                ` + posts + `
            </div>
            `;

        }
        container.innerHTML = insertHTML;
        document.querySelectorAll("button.mpp-accordion").forEach(ele => {
            ele.addEventListener("click", evt => {
                evt.stopPropagation();
                if (!evt.currentTarget.nextElementSibling.querySelectorAll("p>a").length) return;
                let btn = evt.currentTarget;
                let content = btn.nextElementSibling;
                btn.classList.toggle("mpp-accordion-is-open");
                content.classList.toggle("mpp-accordion-is-open");
                content.style.maxHeight = content.classList.contains("mpp-accordion-is-open") ? content.scrollHeight + 'px' : null;
                // evt.currentTarget.querySelector(".mpp-accordion-plus").textContent = content.classList.contains("mpp-accordion-is-open") ? "-" : "+";
            });

        });
        document.querySelectorAll(".mpp-delete.mpp-content-cell").forEach(ele => {
            ele.addEventListener("click", evt => {
                evt.stopPropagation();
                evt.preventDefault();
                if (!confirm("删除后无法撤销,确定删除?")) return;
                const parentButton = evt.currentTarget.closest("button")
                const content = parentButton.nextElementSibling;
                parentButton.remove();
                content.remove();
                MppManager.deleteMarkedThread(evt.currentTarget.dataset.tid);

            })
        });
    }

    function openStatus(evt) {
        evt.stopPropagation();
        evt.preventDefault();
        // 显示数据
        insertDataHTML();

        // 显示菜单
        let sppMenu = document.querySelector(".mpp");
        sppMenu.classList.remove("spp-hide");
        // 显示遮罩
        let sppMenuMask = document.querySelector(".mpp-mask");
        sppMenuMask.classList.remove("spp-hide");
        // 防止滚动到菜单后面的页面
        document.body.style.overflow = "hidden";

        bc.postMessage('mppTaskStart');
        setTimeout(mppTask, 5000);
        sessionStorage.setItem("Soul++:MppTaskID", 'start');
        refreshID = setInterval(insertDataHTML, 1000);
    }

    function closeMenu(evt) {
        if (evt) evt.stopPropagation();
        document.body.style.overflow = null;
        let sppMenu = document.querySelector(".mpp");
        sppMenu.classList.add("spp-hide");
        let sppMenuMask = document.querySelector(".mpp-mask");
        sppMenuMask.classList.add("spp-hide");
        // clearInterval(parseInt(sessionStorage.getItem("Soul++:MppTaskID")));
        sessionStorage.setItem("Soul++:MppTaskID", 'stop');
        clearInterval(refreshID);
    }

    document.querySelector("a.mpp-status").addEventListener("click", openStatus)

    document.querySelector(".mpp-mask").addEventListener("click", closeMenu);


}

function backToTop() {

    const GMKey = "Style_backToTop";
    let backToTopButton = createFloatDraggableButton(
        "回到顶部",
        GMKey,
        {
            left: "calc(50vw + 470px)",
            bottom: "40px",
            background: "linear-gradient(to top, #eeeeee,#ffffff)",
            color: "black",
            fontWeight: "bold",
            outline: "none",
            border: "none",
            borderRadius: "3px",
            width: "30px",
            opacity: "80%",
            cursor: "pointer",
        }
    );

    let dragStart = {};
    let dragEnd = {};

    // 防止拖到视口以外了
    AddIntersectionObserver(([entry]) => {
        if (!entry.isIntersecting) {
            console.log('LEAVE');
            backToTopButton.style.left = dragStart.saved.left;
            backToTopButton.style.bottom = dragStart.saved.bottom;
        }
    }, backToTopButton)

    backToTopButton.addEventListener("click", (e) => {
        e.stopPropagation();
        window.scrollTo({ top: 0, behavior: "smooth" });
    });

    backToTopButton.addEventListener("dragstart", (e) => {
        e.stopPropagation();

        dragStart = {
            clientX: e.clientX,
            clientY: e.clientY,
            saved: {
                left: e.target.style.left,
                bottom: e.target.style.bottom,
            }
        }
    });

    backToTopButton.addEventListener("dragend", (e) => {
        e.stopPropagation();
        // 获得丅的交叉点坐标
        dragEnd = {
            clientX: e.clientX,
            clientY: e.clientY,
        }
        let newLeft = parseFloat(e.target.style.left.match(/(-?\d+)px/)[1]) + (dragEnd.clientX - dragStart.clientX);
        // bottom从下往上算的,所以要减
        let newBottom = parseFloat(e.target.style.bottom.match(/(-?\d+)px/)[1]) - (dragEnd.clientY - dragStart.clientY);


        e.target.style.left = `calc(50vw + ${newLeft}px)`;
        e.target.style.bottom = `${newBottom}px`;

        let tmp = {
            ...GMK.getValue(GMKey),
            ...{
                left: `calc(50vw + ${newLeft}px)`,
                bottom: `${newBottom}px`,
            }
        };
        GMK.setValue(GMKey, tmp);


    });

}

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 highlightViewedThread() {

    function removeCurrent() {
        let prev = document.querySelector(".spp-last-viewed-thread");
        if (prev) prev.classList.remove("spp-last-viewed-thread");
    }

    function setLastViewed() {
        let tmp = GMK.getValue("Soul++:lastViewedThread") || {};
        tmp[fid] = tid;
        GMK.setValue("Soul++:lastViewedThread", tmp);
    }

    function setViewed() {
        let tmp = GMK.getValue("Soul++:viewedThreads") || {};
        tmp[fid] = tmp[fid] || [];
        if (!tmp[fid].includes(tid)) tmp[fid].push(tid);
        GMK.setValue("Soul++:viewedThreads", tmp);
    }

    // 帖子阅读页面处理
    if ((document.location.href.includes("/read.php"))) {
        // 直接打开页面的话不会触发visibilitychange事件
        if (!document.hidden) setViewed();
        // 当visibilitychange触发时,hidden代表用户关闭或者离开了当前页面
        document.addEventListener("visibilitychange", () => {
            if (document.hidden) {
                setLastViewed();
            } else {
                setViewed();
            }
        });
    }
    // 帖子列表页面处理
    else if ((document.location.href.includes("/thread.php") || document.location.href.includes("/thread_new.php"))) {
        // 在帖子列表页面会主动滚动到最后浏览的帖子的位置
        document.addEventListener('readystatechange', (event) => {
            if (document.readyState === "complete") {
                history.scrollRestoration = "manual";
                let ele = document.querySelector(".spp-last-viewed-thread");
                if (!ele) return;
                ele.scrollIntoView({ behavior: "auto", block: "center" });
            }
        });

        // 主动更新帖子列表页
        // GM_addValueChangeListener(name, function(name, old_value, new_value, remote) {})
        GMK.addValueChangeListener("Soul++:viewedThreads", (_name, oldVal, newVal, remote) => {
            console.log(`本版块已阅帖:${newVal[fid]}`);
            document.querySelectorAll(".tr3.t_one").forEach(ele => {
                if (newVal[fid].includes(ele.getAttribute("tid"))) {
                    ele.querySelector("h3 a").classList.add("spp-viewed-thread");
                }
            });
        })
        GMK.addValueChangeListener("Soul++:lastViewedThread", (_name, oldVal, newVal, remote) => {
            // 新记录中当前fid下正在阅读的tid和DOM树中一致的话则返回
            if (document.querySelector(".spp-last-viewed-thread").getAttribute("tid") === newVal[fid]) return;
            console.log(`正在本版块阅读新帖:${newVal[fid]}`);
            removeCurrent();
            document.querySelectorAll(".tr3.t_one").forEach(ele => {
                if (ele.getAttribute("tid") === newVal[fid]) {
                    ele.classList.add("spp-last-viewed-thread");
                    ele.scrollIntoView({ behavior: "auto", block: "center" });
                }
            });
        })

        // 将已经阅读过的帖子改成灰色
        let readedThreads = GMK.getValue("Soul++:viewedThreads") || {};
        let thisForumReadedThreads = readedThreads[fid] || [];
        // console.log(`当前版块已读帖子:${thisForumReadedThreads}`);
        document.querySelectorAll("h3 a").forEach(ele => {
            let container = ele.closest(".tr3.t_one");
            if (!container) return;

            if (thisForumReadedThreads.includes(container.getAttribute("tid"))) {
                console.log(`${container.getAttribute("tid")} 已读`);
                ele.classList.add("spp-viewed-thread");
            }
            if (ele.getAttribute("id") === `a_ajax_${GMK.getValue("Soul++:lastViewedThread")[fid]}`) {
                container.classList.add("spp-last-viewed-thread");
            }
            ele.addEventListener("click", e => {
                e.stopPropagation();
                removeCurrent();
                e.target.closest(".tr3.t_one").classList.add("spp-last-viewed-thread");
            });
        });
    }
}

function createSettingMenu() {
    let menuBox = document.createElement("div");
    document.querySelector("#main").prepend(menuBox);

    let menuButton = document.createElement("li");
    let a = document.createElement("a");
    a.innerText = "Soul++";
    a.style.cursor = "pointer";
    menuButton.appendChild(a);
    a.addEventListener("click", e => {
        e.stopPropagation();
        // 读取数据更显选项显示
        document.querySelectorAll(".spp-accordion-content").forEach(ele => {
            ele.querySelectorAll(".spp-menu-checkbox").forEach(ele => {
                let checkbox = ele.querySelector("input");
                if (checkbox) {
                    let key = checkbox.dataset.funckey;
                    checkbox.checked = GMK.getValue(key);
                }
            })
        });

        // 显示菜单
        let sppMenu = document.querySelector(".spp-menu");
        sppMenu.classList.remove("spp-hide");
        // 显示遮罩
        let sppMenuMask = document.querySelector(".spp-menu-mask");
        sppMenuMask.classList.remove("spp-hide");
        // 防止滚动到菜单后面的页面
        document.body.style.overflow = "hidden";

    });
    document.querySelector("#guide").prepend(menuButton);

    menuBox.outerHTML = `
<div class="spp-menu-mask spp-hide"></div>
<div class="spp-menu spp-hide">
    <div class="spp-menu-container">
        <p class="spp-menu-title">Soul++ 设置</p>
        <div class="spp-menu-main">
            <div class="spp-menu-accordion-op spp-sticky">
                <a class="spp-menu-accordion-support-me" style="grid-column-start: 1">支持作者</a>
                <a class="spp-menu-accordion-expand-all" style="grid-column-start: 4">全部展开</a>
                <a class="spp-menu-accordion-collapse-all" style="grid-column-start: 5">全部折叠</a>
            </div>
            <button type="button" class="spp-accordion spp-accordion-is-open">🔄 免刷新</button>
            <div class="spp-accordion-content spp-accordion-is-open">
                <div class="spp-menu-checkbox"><label><input data-funcKey="buyRefresh_free" type="checkbox" id="buy-refresh-free">购买免刷新</label></div>
            </div>


            <button type="button" class="spp-accordion spp-accordion-is-open">♾️ 无缝加载</button>
            <div class="spp-accordion-content spp-accordion-is-open">
                <div class="spp-menu-checkbox"><label><input data-funcKey="dynamicLoadingThreads" type="checkbox" id="dynamic-load-posts">无缝加载板块帖子列表</label></div>
                <div class="spp-menu-checkbox"><label><input data-funcKey="dynamicLoadingPosts" type="checkbox" id="dynamic-load-threads">无缝加载贴内楼层列表</label></div>
                <div class="spp-menu-checkbox"><label><input data-funcKey="dynamicLoadingSearchResult" type="checkbox" id="dynamic-load-search-result">无缝加载搜索页结果</label></div>
                <div class="spp-menu-checkbox"><label><input data-funcKey="dynamicLoadingPicWall" type="checkbox" id="dynamic-load-pic-wall">无缝加载图墙模式帖子</label></div>
            </div>

            <button type="button" class="spp-accordion spp-accordion-is-open">🛑 屏蔽</button>
            <div class="spp-accordion-content spp-accordion-is-open">
                <div class="spp-menu-checkbox"><label><input data-funcKey="blockAdforumSearchResult" type="checkbox" id="block-adforum-search-result">屏蔽网赚区搜索结果</label></div>
            </div>

            <button type="button" class="spp-accordion spp-accordion-is-open">🔞 SFW安全模式</button>
            <div class="spp-accordion-content spp-accordion-is-open">
                <div class="spp-menu-checkbox"><label><input data-funcKey="hidePostImage" type="checkbox" id="hide-post-image">折叠贴内图片(点击虚线框 展开/折叠 图片)</label></div>
                <div class="spp-menu-checkbox spp-menu-sub-item"><label><input data-funcKey="loadImageOnDemand" type="checkbox" id="load-image-on-demand">按需加载头像、图片(展开后才开始加载)</label></div>
                <div class="spp-menu-checkbox"><label><input data-funcKey="hideForumRules" type="checkbox" id="hide-chaguan-poster">折叠板块公告(其实板块公告右边有个小箭头,我只是帮你们点了一下)</label></div>
                <div class="spp-menu-checkbox"><label><input data-funcKey="hideUserAvatar" type="checkbox" id="hide-user-avatar">替换用户头像为默认(鼠标滑入查看)</label></div>
            </div>
            <button type="button" class="spp-accordion spp-accordion-is-open">🔖 mark++</button>
            <div class="spp-accordion-content spp-accordion-is-open">
                <div class="spp-menu-checkbox"><label><input data-funcKey="markPlusPlus" type="checkbox" id="mark-plus-plus">开启MARK++</label></div>
                <div class="spp-menu-checkbox"><label class="spp-menu-description">- 打开后查看帖子页面右边会出现MARK按钮(可拖到任意位置)</label></div>
                <div class="spp-menu-checkbox"><label class="spp-menu-description">- 点击MARK之后,当前帖子会加入到“我的MARK”列表里</label></div>
                <div class="spp-menu-checkbox"><label class="spp-menu-description">- 在导航栏可以找到“我的MARK”入口,右键点击MARK按钮也可以打开“我的MARK”</label></div>
                <div class="spp-menu-checkbox"><label class="spp-menu-description" style="color: brown">- 保持打开“我的MARK”窗口,脚本会以5秒/帖的频率检查MARK列表</label></div>
                <div class="spp-menu-checkbox"><label class="spp-menu-description" style="color: brown">- 同一时间只允许一个浏览器标签打开“我的MARK”</label></div>
            </div>
            <button type="button" class="spp-accordion spp-accordion-is-open">💠 其它</button>
            <div class="spp-accordion-content spp-accordion-is-open">
                <div class="spp-menu-checkbox"><label><input data-funcKey="automaticTaskCollection" type="checkbox" id="automatic-task-collection">自动领取和完成论坛任务</label></div>
                <div class="spp-menu-checkbox"><label><input data-funcKey="hoistingResourcePost" type="checkbox" id="hoisting-resource-post">将当前页包含[购买/秒传/磁力链/超链]的楼层提升到前面</label></div>
                <div class="spp-menu-checkbox"><label><input data-funcKey="replaceAllDomainToTheSame" type="checkbox" id="replace-all-plus-to-the-same">统一替换所有plus链接为当前正在使用的域名</label></div>
                <div class="spp-menu-checkbox"><label><input data-funcKey="highlightViewedThread" type="checkbox" id="highlight-viewed-threads">标记已阅读过的帖子</label></div>
                <div class="spp-menu-checkbox"><label><input data-funcKey="linkToReplyAndQuote" type="checkbox" id="link-to-reply-and-quote">给[回复第X楼/引用第X楼]增加跳转到该楼层的链接</label></div>
            </div>
            <button type="button" class="spp-accordion spp-danger">❗</button>
            <div class="spp-accordion-content spp-danger-content">
                <button class="spp-btn-danger" data-funcKey="resetAll" id="spp-reset-all">清空所有设置</button>
            </div>

        </div>
        <div class="spp-menu-op-zone">
            <button id="spp-menu-close"><img alt="" src="images/post/smile/smallface/face099.jpg"/> 我好了</button>
        </div>
    </div>
</div>
 
`;

    (function () {
        function closeMenu(evt, saveAndRefresh) {
            evt.stopPropagation();
            document.body.style.overflow = null;
            let sppMenu = document.querySelector(".spp-menu");
            sppMenu.classList.add("spp-hide");
            let sppMenuMask = document.querySelector(".spp-menu-mask");
            sppMenuMask.classList.add("spp-hide");
            if (saveAndRefresh) {
                changes.forEach(e => GMK.setValue(e[0], e[1]))
                document.location.reload();
            }
            changes = [];

        }

        window.addEventListener("keydown", evt => {
            if (evt.key === "Escape") closeMenu(evt, false);
        });
        let changes = [];
        document.querySelectorAll(".spp-menu-checkbox input").forEach(ele => {
            ele.addEventListener("change", evt => {
                changes.push([evt.currentTarget.dataset.funckey, evt.currentTarget.checked]);
            });
        });

        document.querySelector("#spp-menu-close").addEventListener("click", evt => closeMenu(evt, true));
        document.querySelector(".spp-menu-mask").addEventListener("click", evt => closeMenu(evt, false));
        document.querySelector("#spp-reset-all").addEventListener("click", evt => {
            evt.stopPropagation();

            if (confirm(
                `
                【警告】
                这将会清空你在所有的数据和设置,包括:
                - 已读的帖子
                - 可拖放按钮的位置
                - 已经MARK过的帖子
                
                你确定要这样做?`
            )) {
                GMK.listValues().forEach(vName => {
                    console.log(vName);
                    GMK.deleteValue(vName);
                });
                console.log(GMK.listValues());
                document.location.reload();
            }
        });
        document.querySelector(".spp-menu-accordion-support-me").addEventListener("click", evt => {
            evt.stopPropagation();
            let toastTip = Toastify({
                text: "点我或者点击图片即可关闭",
                duration: 15000,
                close: true,
                gravity: "top", // `top` or `bottom`
                position: "center", // `left`, `center` or `right`
                stopOnFocus: true, // Prevents dismissing of toast on hover
                style: {},
                onClick: function () {
                    toastTip.hideToast();
                    toastRedEnvelop.hideToast();
                }
            })
            toastTip.showToast();
            let img = document.createElement("img");
            img.style.background = "none";
            img.style.boxShadow = "none";
            img.style.borderRadius = "10px";
            img.style.width = "300px";
            img.style.height = "435px";
            img.src = 'https://cdn.jsdelivr.net/gh/FetchTheMoon/UserScript/images/RedEnvelope.jpg';
            let toastRedEnvelop = Toastify({
                node: img,
                duration: 9999999,
                close: false,
                gravity: "top", // `top` or `bottom`
                position: "center", // `left`, `center` or `right`
                stopOnFocus: true, // Prevents dismissing of toast on hover
                style: {
                    background: "none",
                    boxShadow: "none",
                },
                onClick: function () {
                    toastTip.hideToast();
                    toastRedEnvelop.hideToast();
                }
            });
            toastRedEnvelop.showToast();
        });
        document.querySelector(".spp-menu-accordion-expand-all").addEventListener("click", evt => {

            document.querySelectorAll(".spp-accordion").forEach(ele => {
                if (ele.classList.contains("spp-danger")) return;
                if (!ele.classList.contains(" spp-accordion-is-open")) ele.classList.add("spp-accordion-is-open");
            });
            document.querySelectorAll(".spp-accordion-content").forEach(ele => {
                if (ele.classList.contains("spp-danger-content")) return;
                if (!ele.classList.contains("spp-accordion-is-open")) ele.classList.add("spp-accordion-is-open");
                ele.style.maxHeight = ele.scrollHeight + 'px';
            });
        });
        document.querySelector(".spp-menu-accordion-collapse-all").addEventListener("click", evt => {
            document.querySelectorAll(".spp-accordion").forEach(ele => {
                if (ele.classList.contains("spp-accordion-is-open")) ele.classList.remove("spp-accordion-is-open")
            });
            document.querySelectorAll(".spp-accordion-content").forEach(ele => {
                if (ele.classList.contains("spp-accordion-is-open")) ele.classList.remove("spp-accordion-is-open")
                ele.style.maxHeight = null;
            });
        });
        document.querySelectorAll("button.spp-accordion").forEach(ele => {
            ele.addEventListener("click", evt => {
                evt.stopPropagation();
                let btn = evt.target;
                let content = btn.nextElementSibling;
                btn.classList.toggle("spp-accordion-is-open");
                content.classList.toggle("spp-accordion-is-open");
                content.style.maxHeight = content.classList.contains("spp-accordion-is-open") ? content.scrollHeight + 'px' : null;
            });

        });
    }());
}

function replaceAllDomainToTheSame() {


    const arr = [...document.querySelectorAll("a")];
    const domains = [
        "spring-plus.net",
        "summer-plus.net",
        "soul-plus.net",
        "south-plus.net",
        "north-plus.net",
        "snow-plus.net",
        "level-plus.net",
        "white-plus.net",
        "imoutolove.me",
        "south-plus.org",
    ];
    const checker = value => domains.some(element => value.href.includes(element));

    arr.filter(checker).filter(ele => !ele.href.includes(window.location.hostname)).forEach(ele => {
        console.log("替换链接:", ele);
        let newURL = new URL(ele.href);
        newURL.hostname = window.location.hostname;
        ele.href = newURL.href;
    });


}

function MutationObserverProcess() {
    function callback(mutationList, observer) {
        mutationList.forEach((mutation) => {
            mutation.addedNodes.forEach(ele => {

                if (ele.tagName === "IMG") {
                    if (ele.classList.contains("spp-mutation-processed")) return

                    function hide(confirmSelector, handler) {
                        const postContainer = ele.closest(confirmSelector);
                        if (!postContainer) return
                        handler(ele);
                        ele.classList.add("spp-mutation-processed");
                    }

                    if (document.location.href.includes("/read.php")) {
                        ele.setAttribute("loading", "lazy");
                        if (GMK.getValue("hideUserAvatar")) hide(".user-pic", hideAvatar)
                        if (GMK.getValue("hidePostImage")) hide(".t5.t2 .r_one", hideImg)
                    }
                }
                if (document.location.href.includes("/read.php") && GMK.getValue("linkToReplyAndQuote")) {
                    if (ele.title === "复制此楼地址") {
                        ele.insertAdjacentHTML("beforebegin", `<a name="SPP-${ele.innerText}" id="SPP-${ele.innerText}"></a>`);
                    }
                }
            });
        });


    }

    let observerOptions = {
        childList: true,  // 观察目标子节点的变化,是否有添加或者删除
        attributes: true, // 观察属性变动
        subtree: true     // 观察后代节点,默认为 false
    }

    let observer = new MutationObserver(callback);
    observer.observe(document.documentElement, observerOptions);
}

async function mppTask() {
    let taskInterval = 5000;
    await (async function () {

        // 先查询页数没到最后一页的
        let markedList = Object.entries(MppManager.getMarkList()).filter(e => {
            return e[1]['maxPage'] - e[1]['page'] > 0
        });
        // 如果都到最后一页了,就按上次查询时间距今最远的来
        if (!markedList.length) {
            markedList = Object.entries(MppManager.getMarkList())
                .sort((f, s) => {
                    return f[1]['lastFetchTime'] - s[1]['lastFetchTime']
                });
        }
        console.log(markedList);
        if (!markedList.length) return;
        if (MppManager.isAllChecked()) taskInterval = 10 * 1000;
        let [_tid, threadStatus] = markedList[0];
        let maxPage = threadStatus['maxPage'] || 1;
        let currentPage = threadStatus["page"] || 1;
        const dummy = await getPage(`/read.php?tid=${_tid}&page=${currentPage}`, true);
        const m = dummy.innerHTML.match(/var totalpage = parseInt\('(\d+)'\)/);
        if (m) maxPage = parseInt(m[1]);
        const allPosts = [...dummy.querySelectorAll(".t5.t2")];

        function getOfferState(dummy) {
            const ele = dummy.querySelector(".tips .s3");
            if (!ele) {
                console.log("没找到悬赏状态", ele, dummy);
                return;
            }
            const state = ele.textContent;
            if (state.includes("剩余时间:已结束")) {
                return "悬赏超时";
            } else if (state.includes("悬赏结束")) {
                return `<a class="mpp-sell" href='/read.php?tid=${_tid}' target="_blank" style="color: #73a5ff">有答案了</a>`;
            } else if (state.includes("悬赏中")) {
                return `剩余${state.match(/(\d+)小时/)[1]}小时`;
            }
        }

        if (currentPage === 1) {
            threadStatus["offerState"] = getOfferState(dummy);
            allPosts.shift();
        }
        // 在第一页获取一下帖子的状态,看看到底有没有结贴
        if (threadStatus["allPagesChecked"]) {
            await sleep(5000); // 否则会遇到提示刷新小于1秒;
            const page1dummy = await getPage(`/read.php?tid=${_tid}&page=1`, true);
            threadStatus["offerState"] = getOfferState(page1dummy);
        }
        threadStatus["sell"] = threadStatus["sell"] || [];
        threadStatus["hyperlink"] = threadStatus["hyperlink"] || [];
        threadStatus["magnetOrMiaochuan"] = threadStatus["magnetOrMiaochuan"] || [];
        threadStatus['title'] = dummy.querySelector('.crumbs-item.current strong>a').textContent;
        threadStatus['lastFetchTime'] = getTimeStamp();
        threadStatus['maxPage'] = maxPage;
        threadStatus["page"] += currentPage < maxPage ? 1 : 0;
        const getPid = (post) => {
            return post.previousSibling.getAttribute("name");
        }
        allPosts.forEach(post => {

            let u = `/read.php?tid=${_tid}&page=${currentPage}#${getPid(post)}`;
            if (post.querySelector(".quote.jumbotron")) {
                if (!threadStatus["sell"].includes(u)) threadStatus["sell"].push(u)
            } else if (post.querySelector(".tpc_content a")) {
                if (!threadStatus["hyperlink"].includes(u)) threadStatus["hyperlink"].push(u);
            } else if (post.querySelector(".tpc_content").textContent.match(/^[0-9a-fA-F]{20,}/mg)) {
                if (!threadStatus["magnetOrMiaochuan"].includes(u)) threadStatus["magnetOrMiaochuan"].push(u);
            }
        });


        threadStatus["allPagesChecked"] = currentPage === maxPage;
        if (MppManager.isThreadExist(_tid)) MppManager.setThreadStatus(_tid, threadStatus);
        console.log(threadStatus);

    })();
    if (sessionStorage.getItem("Soul++:MppTaskID") === 'start') setTimeout(mppTask, taskInterval);

}

function linkToReplyAndQuote(target = document) {
    if (window.location.hash.includes("#SPP-")) {
        const ele = document.querySelector(window.location.hash);
        if (ele) ele.scrollIntoView();
    }

    let allPosts = [...target.querySelectorAll(".t5.t2")];
    allPosts.forEach(ele => {
        const quote = ele.querySelector("h6.quote2+div");
        if (quote) {
            const floor = parseInt(quote.firstChild.textContent.match(/引用第(\d+)楼/)[1]);
            const page = Math.ceil(floor / 30);
            const text = quote.firstChild.textContent.replace(/引用(第\d+楼)(.+)/, `引用<a style="color: dodgerblue" href="/read.php?tid=${tid}&page=${page}#SPP-B${floor}F">$1</a>$2`);
            quote.removeChild(quote.firstChild);
            quote.insertAdjacentHTML("afterbegin", text);

        }
        const reply = ele.querySelector(".h1.fl>.fl");
        if (reply) {
            const m = reply.firstChild.textContent.match(/回 (\d+)楼/);
            if(!m) return
            const floor = parseInt(m[1]);
            const page = Math.ceil(floor / 30);
            const text = reply.innerText.replace(/回 (\d+)楼(.+)/, `回 <a style="color: dodgerblue" href="/read.php?tid=${tid}&page=${page}#SPP-B${floor}F">$1楼</a>$2`);
            reply.innerText = "";
            reply.insertAdjacentHTML("afterbegin", text);

        }
    });
}

function hoistingResourcePost(target = document) {
    if (document.location.href.includes("#")) return;
    let allPosts = [...target.querySelectorAll(".t5.t2")];

    // 是否拿出顶楼
    const insertPosition = page === 1 ? allPosts.shift() : target.querySelector("input[name=tid]");
    const resourcePosts = allPosts.filter(post =>
        post.querySelector(".quote.jumbotron")
        || post.querySelector(".tpc_content a")
        || post.querySelector(".tpc_content").textContent.match(/^[0-9a-fA-F]{20,}/mg)
    ).reverse();
    resourcePosts.forEach(ele => insertPosition.after(ele));

}

function AddIntersectionObserver(callback, element) {
    const obs = new window.IntersectionObserver(callback, {
        root: null,
        threshold: 0.5,
    })
    obs.observe(element);
}

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

    GMK.addStyle(`<style>
    .spp-last-viewed-thread{
        background: #deeeff;
    }
    .spp-viewed-thread{
        color: #bbbbbb;
    }
    .spp-hide{
        display: none;
    }
    
    
        
    .spp-menu{
        position: fixed;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        background: white; 
        z-index: 2000000; 
    }
    .spp-menu-mask{
        position: fixed;
        left: 0;
        top: 0;
        width: 100%;
        height: 100%;
        background: black;
        opacity: 0.5;
        user-select: none;
        z-index: 1000000; 
    }
    .spp-menu-container{
        background: #eeeeee;
        display:grid;
        grid-template-areas:
            "title"
            "main"
            "op";
        grid-template-rows: 28px auto 40px;
        grid-gap: 10px;
        min-height: 80vh;
        max-height: 80vh;
        width: 600px; 
    }
    .spp-menu-title{
        grid-area: title;
        background: url(images/colorImagination/bg_topnav.gif) repeat-x #000000;
        border-left: black;
        border-right: black;
        text-align: center;
        font-weight: bold;
        height: 100%;
        color: white;
        padding-top: 5px;
        margin: 0;
    }
    
    .spp-menu-main{
        grid-area: main;
        height: 70vh;
        overflow-y: scroll;
    }
    
    /*.spp-menu-op-zone{*/
    /*    grid-area: op;*/
    /*    display: grid;*/
    /*    grid-template-columns: 50% 50%;*/
    /*}*/
    
    .spp-hide{
        display: none;
    }
    
    button.spp-accordion{
        width: 100%;
        border: none;
        outline: none;
        background-color: whitesmoke;
        text-align: left;
        padding: 10px 10px;
        font-size: 14px;
        font-weight: bold;
        color: #444;
        cursor: pointer;
        transition: background-color 0.2s linear;
    }
    
    button.spp-accordion:after{
        content:'+';
        font-size: 14px;
        float: right;
    }
    
    button.spp-accordion.spp-accordion-is-open:after{
        content: '-';
    }
    
    button.spp-accordion:hover, button.spp-accordion.spp-accordion-is-open{
        background-color: #ddd;
    }
    
    .spp-accordion-content{
        background: #eeeeee;
        border-left: 1px solid whitesmoke;
        border-right: 1px solid whitesmoke;
        padding: 0 20px;
        margin-bottom: 3px;
        max-height: 0;
        overflow: hidden;
        /*transition: all 0.2s ease-in-out;*/
        font-size: 14px;
    }
    
    .spp-accordion-content.spp-accordion-is-open{
        max-height: fit-content;
        /*transition: all 0.2s ease-in-out;*/
    }
    
    .spp-sticky{
        position: sticky;
        top: 0;
    }
    
    .spp-menu-accordion-op{
        display: grid;
        grid-template-columns: repeat(5,1fr);
        justify-content: end;
        align-content: center;
    }
    .spp-menu-accordion-op a{
        padding: 5px;
        margin-left: 20px;
        font-size: 12px;
        cursor: pointer;
    }
    
    .spp-menu-checkbox{
        margin: 10px;
    }
    .spp-menu-checkbox input[type="checkbox"]{
        margin-bottom: 5px;
    }
    /*.spp-menu-op-zone{*/
    
    /*    display: flex;*/
    /*    justify-content: space-around;*/
    /*    align-content: center;*/
    /*}*/
    
    .spp-menu-op-zone button{
        display: block;
        width: 100%;
        height: 100%;
        background: black;
        color: white;
        border: 1px solid black;
        font-size: 16px;
        font-family: 宋体,"sans-serif";
        font-weight: bold;
        cursor: pointer;
        
    }
    
    #spp-menu-close{
       background:linear-gradient(to bottom, #3a3a3a,#000000);
    }
     
    .spp-menu-description{
        padding-left: 20px;
        font-size: 12px;
    }
    
    .spp-danger-content{
        padding: 0;
    }
    
    .spp-btn-danger{
        display: block;
        background: crimson;
        color: whitesmoke;
        width: 100%;
        height: 40px;
        border: none;
        outline: none;
        cursor: pointer;
    }
    .spp-menu-sub-item{
        padding-left: 20px;
    }
        .spp-loading-animation{
            width:150px;
            margin:50px auto;
            text-align: center;
        }
        .spp-loading-animation >div{
            width: 18px;
            height: 18px;
            border-radius: 100%;
            display:inline-block;
            background-color: #af0909;
            -webkit-animation: dot 1.4s infinite ease-in-out;
            animation: dot 1.4s infinite ease-in-out;
            -webkit-animation-fill-mode: both;
            animation-fill-mode: both;
        }
        .spp-loading-animation .dot1{
            -webkit-animation-delay: -0.30s;
            animation-delay: -0.30s;
        }
        .spp-loading-animation .dot2{
            -webkit-animation-delay: -0.15s;
            animation-delay: -0.15s;
        }
        @-webkit-keyframes dot {
            0%, 80%, 100% {-webkit-transform: scale(0.0) }
            40% { -webkit-transform: scale(1.0) }
        }
        @keyframes dot {
            0%, 80%, 100% {-webkit-transform: scale(0.0) }
            40% { -webkit-transform: scale(1.0) }
        }
    </style>`.replace(/<\/?style>/gm, ""));
    GMK.addStyle(GMK.getResourceText("TOASTIFY_CSS"));

    console.log(`=======================================
            Soul++ 已经启动
=======================================`);
    // 给所有图片增加懒加载,以及处理图片隐藏
    MutationObserverProcess();

    // toast("Soul++ 已启动,可以在论坛导航栏进行设置", ToastType.WARNING, 5000);
    // toast("Soul++ 已启动,可以在论坛导航栏进行设置", ToastType.INFO, 5000);
    // toast("Soul++ 已启动,可以在论坛导航栏进行设置", ToastType.SUCCESS, 5000);
    // toast("Soul++ 已启动,可以在论坛导航栏进行设置", ToastType.DANGER, 5000);

    document.addEventListener("readystatechange", evt => {
        if (!(document.readyState === "interactive")) return;
        createSettingMenu();
        backToTop();

        if (document.location.href.includes("/read.php")) {
            postAddAnchorAttribute(document, page, tid);
            if (GMK.getValue("hoistingResourcePost")) {
                hoistingResourcePost();
            }
            if (GMK.getValue("linkToReplyAndQuote")) {
                linkToReplyAndQuote();
            }
        }
        if (document.location.href.includes("/thread.php")) {
            threadAddAnchorAttribute(document, page, fid);
            if (GMK.getValue("hideForumRules")) {
                document.cookie = "deploy=%09thread%09%0A;"
                document.querySelector("#cate_thread").style.display = "none";
            } else {
                document.cookie = "deploy=;"
                document.querySelector("#cate_thread").style.display = null;
            }
        }

        if (GMK.getValue("buyRefresh_free") && document.location.href.includes("/read.php")) {
            buyRefresh_free();
        }
        if (GMK.getValue("dynamicLoadingThreads") && (document.location.href.includes("/thread.php"))) {
            dynamicLoadingNextPage(PageType.THREADS_PAGE);
        }
        if (GMK.getValue("dynamicLoadingPicWall") && document.location.href.includes("/thread_new.php")) {
            dynamicLoadingNextPage(PageType.PIC_WALL_PAGE);
        }
        if (GMK.getValue("dynamicLoadingPosts") && document.location.href.includes("/read.php")) {
            dynamicLoadingNextPage(PageType.POSTS_PAGE);
        }
        if (GMK.getValue("dynamicLoadingSearchResult") && document.location.href.includes("/search.php")) {
            dynamicLoadingNextPage(PageType.SEARCH_RESULT);
        }
        if (GMK.getValue("blockAdforumSearchResult") && document.location.href.includes("/search.php")) {
            blockAdforumSearchResult();
        }
        if (GMK.getValue("automaticTaskCollection")) {
            automaticTaskCollection();
        }
        if (GMK.getValue("highlightViewedThread")) {
            highlightViewedThread();
        }
        if (GMK.getValue("replaceAllDomainToTheSame")) {
            replaceAllDomainToTheSame();
        }
        if (GMK.getValue("markPlusPlus")) {
            mark();
        }

    });


})();