Greasy Fork

来自缓存

Greasy Fork is available in English.

笔趣阁优化

专注阅读

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         笔趣阁优化
// @namespace    https://gitee.com/linhq1999/OhMyScript
// @version      5.2
// @description  专注阅读
// @author       LinHQ
// @match        http*://www.shuquge.com/*.html
// @exclude      http*://www.shuquge.com/*index.html
// @match        http*://www.sywx8.com/*.html
// @match        http*://www.biqugetv.com/*.html
// @match        http*://www.bqxs520.com/*.html
// @match        https://www.dshfood.net/*.html
// @grant        GM_addStyle
// @grant        GM_openInTab
// @grant        GM_xmlhttpRequest
// @require      http://greasyfork.icu/scripts/427726-gbk-url-js/code/GBK_URLjs.js?version=953098
// @inject-into  auto
// @license MIT
// ==/UserScript==
'use strict';
/** 配置示例
* 建议在定制 search 函数时, rq 函数始终把参数写全
* "sites": [
*   {
*       "desc": "shuquge", 网站链接关键字
*       "url": "https://....", 网站首页链接
*       "main": "div.reader", 主要部分选择器
*       "title": ".reader h1", 标题选择器
*       "txt": "#content", 文字部分选择器
*       "toc": "dd a", 目录链接选择器
*       "tocJump": 12, 跳过前面多少章
*       "filter": ["div.header", "div.nav", "div.link"], 带有此选择器的元素将被删除
*       "txtfilter": ["shuqu"] 带有此关键字的行将被删除
*       "funcFilter"?: () => void, 自定义过滤器
*       "nodash"?: boolean, 判断是否应该在书籍详情页链接后加额外的斜杠
*       "search"?: (keywords: string, baseurl:string) => Promise<Link[]> 搜索行为
*   }
* ]
*/
(() => {
    // 缺省值,一般不用修改
    const lineHeight = 1.3;
    // const defaultFont = "楷体";
    const defaultFont = "Source Han Sans SC VF";
    let C = {
        "sites": [
            {
                "desc": "shuquge",
                "url": "https://www.shuquge.com/",
                "main": "div.reader",
                "title": ".reader h1",
                "txt": "#content",
                "toc": "dd a",
                "tocJump": 12,
                "filter": [
                    "div.header", "div.nav", "div.link", "img",
                    "#coupletleft", "#coupletright", "#HMRichBox"
                ],
                "txtfilter": ["shuqu"],
                "funcFilter": () => { var _a, _b; return (_b = (_a = fd(document, "#content")) === null || _a === void 0 ? void 0 : _a.previousSibling) === null || _b === void 0 ? void 0 : _b.remove(); }
            },
            {
                "desc": "sywx",
                "url": "https://www.sywx8.com/",
                "main": "div#container",
                "title": "div>h1",
                "toc": "li a",
                "tocJump": 0,
                "txt": "div#BookText",
                "filter": ["div.top", ".link.xb", "#footer"],
                "txtfilter": ["最快更新", "松语", "本章完", "本章未完"],
                // javascript 不支持 gbk 的 uri 编码,所以无法实现
                // 但是用 gbk.js 就不一样了
                "search": async (keywords, baseurl) => {
                    let links = [];
                    let doc = await rq({
                        "url": `https://www.sywx8.com/modules/article/search.php?searchkey=${$URL.encode(keywords)}`
                    }, 8000, "GBK");
                    for (let a of doc.querySelectorAll(".c_row .c_subject a")) {
                        // 这个网站比较特殊,链接默认是完整的
                        links.push({ "title": `(sywx) ${a.textContent}`, "href": attr(a, "href") });
                    }
                    return links;
                }
            },
            {
                "desc": "bqxs",
                "url": "http://www.bqxs520.com/",
                "main": ".box_con",
                "title": "div.content_read h1",
                "toc": "#list dd a",
                "tocJump": 9,
                "txt": "#content",
                "filter": [".ywtop", ".header", ".nav", ".bottem1", ".lm", "#page_set", ".bookname~.box_con"],
                "txtfilter": ["请记住本书", "http"],
                "search": async (keywords, baseurl) => {
                    let links = [];
                    let doc = await rq({
                        "method": "POST",
                        "headers": { "Content-Type": "application/x-www-form-urlencoded" },
                        "url": encodeURI(`http://www.bqxs520.com/case.php?m=search`),
                        "data": `&key=${encodeURI(keywords)}`
                    }, 7000, "UTF-8");
                    for (let a of doc.querySelectorAll(".l .s2 a")) {
                        links.push({ "title": `(bqxs) ${a.textContent}`, "href": concatURL(baseurl, attr(a, "href")) });
                    }
                    return links;
                }
            },
            {
                "desc": "biqugetv",
                "url": "https://www.biqugetv.com/",
                "main": ".box_con",
                "title": "div.content_read h1",
                "toc": "#list dd a",
                "tocJump": 0,
                "txt": "#content",
                "filter": [".ywtop", ".header", ".nav", ".bottem1", ".lm", "#page_set"],
                "txtfilter": [],
                "search": async (keywords, baseurl) => {
                    let links = [];
                    let doc = await rq({
                        "url": encodeURI(`https://www.biqugetv.com/search.php?keyword=${keywords}`)
                    }, 6000, "UTF-8");
                    for (let a of doc.querySelectorAll("h3 a")) {
                        links.push({ "title": `(biqugetv) ${a.textContent}`, "href": concatURL(baseurl, attr(a, "href")) });
                    }
                    return links;
                }
            },
            {
                "desc": "dshfood",
                "url": "https://www.dshfood.net/",
                "main": ".box_con",
                "title": "div.content_read h1",
                "toc": "#list dd a",
                "tocJump": 9,
                "txt": "#content",
                "filter": [".ywtop", ".header", ".nav", ".bottem1", "#page_set", "#content>div"],
                "txtfilter": ["笔趣阁"],
                "nodash": true,
                "funcFilter": () => document.querySelectorAll("img")
                    .forEach(e => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.remove(); }),
                "search": async (keywords, baseurl) => {
                    let links = [];
                    let doc = await rq({
                        "method": "POST",
                        "headers": {
                            "Content-Type": "application/x-www-form-urlencoded",
                            "referer": "https://www.dshfood.net/so/"
                        },
                        "url": "https://www.dshfood.net/so/",
                        // 鉴于使用了 GBK 进行编码,不能再使用 URLSearchParams
                        "data": `?searchtype=articlename&searchkey=${$URL.encode(keywords)}&submit=`
                    }, 6000, "GBK");
                    for (let a of doc.querySelectorAll(".line a.blue")) {
                        links.push({ "title": `(dshfood) ${a.textContent}`, "href": concatURL(baseurl, attr(a, "href")) });
                    }
                    return links;
                }
            }
        ],
        "states": {
            "fontSize": 16,
            "lineHeight": 16 * lineHeight,
            "toc": false,
            "flow": false
        },
        "style": `
            body {
                background-color: #EAEAEF !important;
            }

            .bqg.inject.win {
                width: 55vw !important;
                min-width: 600px;
                border: 2px double gray !important;
                border-radius: 8px;
            }

            .bqg.inject.txt {
                font-family: Calibri,'${defaultFont}',serif!important;
                background-color: #EAEAEF !important;
                padding: 0.5em 1em !important;
                margin: 0.5em auto !important;
                width: auto !important;
                white-space: pre-wrap;
            }

            .bqg.inject.title {
                color: black;
                background-color: #EAEAEF;
                font-family: Calibri,'${defaultFont}',serif!important;
                cursor: pointer !important;
            }

            .bqg.inject.title:hover {
                color: #0258d8 !important;
            }
            
            .hq.inject.toc {
                font-family: Calibri,sans-serif;
                width: 275px;
                position: fixed;
                top: 30px;
                left: 8px;
                /*目录默认是关闭的*/
                transform: translateX(-300px);
                opacity: 0;
                padding: 5px;
                display: flex;
                flex-flow: column;
                box-shadow: #7b7b7b 5px 4px 5px;
                transition-property: transform, box-shadow, opacity;
                transition-duration: .5s;
                transition-timing-function:cubic-bezier(0.35, 1.06, 0.83, 0.99);
                background: rgb(246 246 246 / 60%);
                backdrop-filter: blur(2px);
                border-radius: 8px;
            }

            .hq.inject ul {
                height: 280px;
                width: 100%;
                /*offsetTop 计算需要*/
                position:relative;
                overflow: auto;
            }

            .hq.inject ul li {
                cursor: pointer;
                margin: 2px;
                width: 95%;
                padding: 1px 4px;
                font-size: 12px;
                border-radius: 4px;
            }

            .hq.inject ul li:hover {
                background: #0258d8;
                color: #f6f6f6;
            }

            .hq.inject.toc>h3 {
                font-size: 1.1rem;
                font-weight: bold;
                border-radius: 2px;
                align-self: center;
                cursor: pointer;
                margin: 4px 0 8px 0;
            }

            .hq.inject.toc>h3:hover {
                color: #ffa631 !important;
            }

            .hq.inject.search {
                font-family: Calibri,sans-serif;
                width: 275px;
                position: fixed;
                top: 30px;
                padding: 5px;
                display: flex;
                flex-flow: column;
                transition: right 0.5s cubic-bezier(0.35, 1.06, 0.83, 0.99);
                background: rgb(246 246 246 / 60%);
                border-radius: 8px;
            }

            .hq.inject.search input {
                margin: 8px auto;
                width: 95%;
            }
            `
    };
    // 查询已经保存的字体信息
    let savedStates = localStorage.getItem("bqg_cfg");
    // 检查是否存在已有设置且和当前版本相符
    let states;
    if (savedStates === null) {
        states = C.states;
        console.warn("当前状态已保存");
    }
    else {
        let cfg = JSON.parse(savedStates);
        let defaultStates = Object.keys(C.states);
        let cfg_ = Object.keys(cfg);
        let useSaved = true;
        // 检查键是否匹配
        if (defaultStates.length == cfg_.length) {
            for (let key of Object.keys(cfg)) {
                if (!defaultStates.includes(key)) {
                    useSaved = false;
                    break;
                }
            }
        }
        else {
            useSaved = false;
        }
        if (useSaved) {
            states = cfg;
        }
        else {
            states = C.states;
            console.warn("检测到版本变化,状态已重置");
        }
    }
    // 检测当前的网址,应用对应的设置
    let tmp = C.sites.filter(site => document.URL.includes(site.desc));
    if (tmp.length == 0) {
        console.warn("没有匹配的设置,脚本已终止!");
        return;
    }
    let currentSite = tmp[0];
    // 完成样式注入
    GM_addStyle(C.style);
    /**
     * 保存交互式状态
     */
    function saveStates() {
        localStorage.setItem("bqg_cfg", JSON.stringify(states));
    }
    /**
     * 上一章,同时移除所有 flow 拼接结果
     */
    function prevChapter() {
        var _a;
        (_a = fd(document, "a", "上一")) === null || _a === void 0 ? void 0 : _a.click();
    }
    /**
     * 下一章,同时移除所有 flow 拼接结果
     */
    function nextChapter() {
        var _a;
        (_a = fd(document, "a", "下一")) === null || _a === void 0 ? void 0 : _a.click();
    }
    /**
     * 异步,向下拼页
     * 绑定到事件上时务必注意重复触发的情况
     */
    async function concatNextCh() {
        var _a;
        let next = fd(document, "a", "下一");
        let prev = fd(document, "a", "上一");
        let currentText = fd(document, currentSite.txt);
        try {
            let doc = await rq({ url: next === null || next === void 0 ? void 0 : next.href });
            let text = fd(doc, currentSite.txt);
            // console.log(text.textContent)
            // 更好的性能
            currentText.insertAdjacentHTML("beforeend", "<br><hr style='border: unset;border-top: 1px solid gray; margin: ${states.lineHeight}px 0'>");
            currentText.insertAdjacentText("beforeend", txtFilter((_a = text.innerText) !== null && _a !== void 0 ? _a : "文本过滤错误", /(?![a-zA-Z0-9!.'"])\s+/));
            // /id/xxx_1.html -> /id/xxx_1
            let href = attr(next, "href").replace(/\.html$/, "");
            // 重新渲染目录,currentBookToc 不可能为 null
            renderTOC(JSON.parse(currentBookToc), ul, href);
            // 重设上一页和下一页按钮的链接
            prev.href = fd(doc, "a", "上一").href;
            next.href = fd(doc, "a", "下一").href;
        }
        catch (error) {
            currentText.innerText = currentText.innerText.concat("\n\n\t获取下一页错误,上下滚动以重新获取");
        }
    }
    // 目录切换
    function switchToc(open) {
        let toc = fd(document, ".hq.inject.toc");
        if (open) {
            toc.style.transform = "translateX(0)";
            toc.style.opacity = "1";
            toc.style.boxShadow = "box-shadow: #7b7b7b 5px 3px 4px 0px;";
            states.toc = true;
        }
        else {
            toc.style.transform = "translateX(-300px)";
            toc.style.opacity = "0";
            toc.style.boxShadow = "box-shadow: #7b7b7b 5px 2px 0px 0px;";
            states.toc = false;
        }
        saveStates();
    }
    // 目录开关
    function toggleToc() {
        if (states.toc) {
            switchToc(false);
        }
        else {
            switchToc(true);
        }
    }
    /**
     * 根据 site 中的条件进行过滤,同时将缩进统一
     *
     * @param itxt 需要过滤的,innerText 通用性最好
     * @param delim 默认的切分点,从网页解析得到的内容和 ajax 获取到的内容切分点不一致
     * @returns 过滤后字符串
     */
    function txtFilter(itxt, delim = /\n/g) {
        var _a;
        // innerText 相对于 textContent 保留了视觉上的换行(块的换行)
        return (_a = itxt === null || itxt === void 0 ? void 0 : itxt.split(delim)) === null || _a === void 0 ? void 0 : _a.filter(line => {
            if (/^\s*$/.test(line))
                return false;
            // 去除白行和包含的关键字
            for (const keyword of currentSite.txtfilter) {
                if (line.includes(keyword)) {
                    return false;
                }
            }
            return true;
        }).map(line => `${" ".repeat(2)}${line.trim()}`).join("\n\n");
    }
    if (states.flow) {
        // 变相 throttle 一下不然顶不住
        let loading = false;
        document.onscroll = async (_) => {
            if (!loading && chkBoundry(true, window.innerHeight * 0.75)) {
                loading = true;
                // 意思是上一次拼页完过1.5秒才允许继续拼页,避免在加载下一页时反复调用拼页函数
                // 效果比固定延迟要稳定
                await concatNextCh();
                setTimeout(() => { loading = false; }, 1500);
            }
        };
    }
    // 对可变部分产生影响
    let doInject = function () {
        var _a;
        // 执行元素过滤
        currentSite.filter.forEach(filter => { var _a; return (_a = document.querySelectorAll(filter)) === null || _a === void 0 ? void 0 : _a.forEach(ele => ele.remove()); });
        // 执行自定义过滤
        if (currentSite.funcFilter) {
            currentSite.funcFilter();
        }
        // 应用已经保存的状态
        let textWin = fd(document, currentSite.txt);
        textWin.setAttribute("style", `font-size:${states.fontSize}px;line-height:${states.lineHeight}px`);
        textWin.classList.add("bqg", "inject", "txt");
        // 执行文字过滤
        textWin.textContent = txtFilter((_a = textWin.innerText) !== null && _a !== void 0 ? _a : "文本过滤错误");
        let mainWin = fd(document, currentSite.main);
        mainWin.classList.add("bqg", "inject", "win");
        let title = fd(document, currentSite.title);
        title.title = "点击显示目录";
        title.classList.add("bqg", "inject", "title");
        title.onclick = (ev) => {
            toggleToc();
            // 避免跳到上一章
            // 比下面的更为具体,所以有效。
            ev.stopPropagation();
        };
        // 阻止双击事件被捕获(双击会回到顶部)
        document.body.ondblclick = (ev) => ev.stopImmediatePropagation();
        document.body.onclick = (ev) => {
            let root = document.documentElement;
            let winHeight = window.innerHeight;
            // 下半屏单击下滚,反之上滚
            if (ev.clientY > root.clientHeight / 2) {
                if (chkBoundry() && !states.flow)
                    nextChapter();
                window.scrollBy({ top: (window.innerHeight - lineHeight) * 1 });
            }
            else {
                if (chkBoundry(false)) {
                    prevChapter();
                }
                window.scrollBy({ top: (window.innerHeight - lineHeight) * -1 });
            }
        };
        document.body.onkeydown = (ev) => {
            switch (ev.key) {
                case "-":
                    states.fontSize -= 2;
                    textWin.style.fontSize = `${states.fontSize}px`;
                    states.lineHeight = states.fontSize * lineHeight;
                    textWin.style.lineHeight = `${states.lineHeight}px`;
                    saveStates();
                    break;
                case "=":
                    states.fontSize += 2;
                    textWin.style.fontSize = `${states.fontSize}px`;
                    states.lineHeight = states.fontSize * lineHeight;
                    textWin.style.lineHeight = `${states.lineHeight}px`;
                    saveStates();
                    break;
                case "j":
                    if (chkBoundry() && !states.flow) {
                        nextChapter();
                    }
                    else {
                        window.scrollBy({ top: window.innerHeight - states.lineHeight });
                    }
                    break;
                case "k":
                    // 考虑在 flow 模式下也允许上一章
                    if (chkBoundry(false) && !states.flow) {
                        prevChapter();
                    }
                    else {
                        window.scrollBy({ top: -1 * (window.innerHeight - states.lineHeight) });
                    }
                    break;
                case "h":
                    prevChapter();
                    break;
                case "l":
                    nextChapter();
                    break;
                case "t":
                    toggleToc();
                    break;
                case "s":
                    toggleSearch();
                    break;
                case "f":
                    states.flow = !states.flow;
                    saveStates();
                    break;
                default:
                    break;
            }
        };
    };
    // 先调用一次,后面是有变化时才会触发,避免有时无法起作用
    doInject();
    // 强力覆盖
    new MutationObserver((_, ob) => {
        doInject();
    }).observe(document.body, { childList: true });
    // 添加目录
    let toc = document.createElement("div");
    toc.className = "hq inject toc";
    toc.onclick = ev => ev.stopPropagation();
    // 已保存状态读取
    document.body.append(toc);
    if (states.toc)
        switchToc(true);
    // 目录状态指示灯
    let pointer = document.createElement("h3");
    // 当然也可以靠不同类名实现
    let pointerColors = { "loaded": "#afdd22", "loading": "#ffa631", "unload": "#ed5736" };
    pointer.title = "点击以重新加载目录";
    pointer.innerHTML = "目<span style='display: inline-block;width: 1em'></span>录";
    pointer.style.cursor = "pointer";
    pointer.style.color = pointerColors.unload;
    toc.append(pointer);
    // 目录列表
    let ul = document.createElement("ul");
    toc.append(ul);
    /**
     * 从源渲染目录到指定元素
     *
     * @param toc 目录源
     * @param ul 容器
     * @param href 定位链接,格式 http://host/id/chp.html 中最短为 /id/chp 部分
     */
    function renderTOC(toc, ul, href) {
        var _a;
        // 清空旧内容
        ul.innerHTML = "";
        let current = null;
        // 进度计数器
        let counter = 1;
        for (let lnk of toc) {
            let li = document.createElement("li");
            li.textContent = lnk.title;
            // 根据传入的 href 是否包含目录中的链接来判定,因为有的网站包含子页面 XXXX_1.html 形式
            // 比对时标准目录链接 lnk: /id/chp.html 之中,仅取用 chp
            let last = (_a = lnk.href.replace(".html", "")) !== null && _a !== void 0 ? _a : "";
            if (current == null && href.includes(last)) {
                li.innerHTML = `${lnk.title}<span style="flex: 1;"></span>${(counter / toc.length * 100).toFixed(1)}%`;
                current = li;
            }
            li.onclick = (ev) => {
                document.location.href = lnk.href;
                ev.stopPropagation();
            };
            ul.append(li);
            counter++;
        }
        // 渲染完修改指示灯状态
        pointer.style.color = pointerColors.loaded;
        // 滚动到当前位置,并高亮
        if (current !== null) {
            current.setAttribute("style", "display:flex;font-weight:bold;background: #0258d8;color: #f6f6f6;");
            ul.scrollTo({ top: current.offsetTop - 130 });
        }
    }
    /**
     * 获取目录信息
     *
     * @param currentBookLink 当前书的链接,用作存储的键
     * @param pointer 指示灯,在需要的时候修改状态
     */
    async function fetchTOC(currentBookLink, pointer) {
        var _a;
        // 修改指示灯状态
        pointer.style.color = pointerColors.loading;
        try {
            let doc = await rq({ url: currentBookLink });
            let tocs = doc.querySelectorAll(currentSite.toc);
            let data = [];
            // 序列化存储准备
            for (let link of tocs) {
                // 使用字面意义上的链接 /chapter.html 而不是 http://**/id/chapter.html 以减小存储量
                data.push({ "title": (_a = link.textContent) !== null && _a !== void 0 ? _a : "", "href": attr(link, "href") });
            }
            if (currentSite.tocJump)
                data = data.slice(currentSite.tocJump);
            // 缓存目录信息
            let stdata = JSON.stringify(data);
            sessionStorage.setItem(currentBookLink, stdata);
            // 更新变量,避免章节拼接时以为找不到
            currentBookToc = stdata;
            renderTOC(data, ul, href);
        }
        catch (_) {
            pointer.style.color = pointerColors.unload;
        }
    }
    let source = document.URL.split("/");
    source.pop();
    // 用来定位的 url
    let href = document.URL.replace(/\.html$/, "");
    // 最后加斜杠保险
    let currentBook = source.join("/");
    if (!currentSite.nodash) {
        currentBook += "/";
    }
    let currentBookToc = sessionStorage.getItem(currentBook);
    if (currentBookToc === null) {
        fetchTOC(currentBook, pointer);
    }
    else {
        renderTOC(JSON.parse(currentBookToc), ul, href);
    }
    // 单击指示灯刷新目录缓存
    pointer.onclick = _ => fetchTOC(currentBook, pointer);
    // 添加聚合搜索
    let searchBox = document.createElement("div");
    searchBox.onclick = ev => ev.stopPropagation();
    searchBox.onkeydown = ev => ev.stopPropagation();
    searchBox.className = "hq inject search";
    searchBox.style.right = "-300px";
    searchBox.innerHTML = `
    <input id="insearch" type="search" placeholder="至少输入两个字"/>
    <span style="align-self:center;margin-bottem: 4px;color: ${pointerColors.loaded}">已就绪</span>
    `;
    document.body.append(searchBox);
    let inputBox = fd(searchBox, "#insearch");
    let search_ul = document.createElement("ul");
    searchBox.append(search_ul);
    let search_pointer = fd(searchBox, "#insearch~span");
    // debounce 一下不然顶不住
    let timer = null;
    inputBox.oninput = _ => {
        if (timer !== null)
            clearTimeout(timer);
        timer = setTimeout(async () => {
            var _a, _b;
            // 放外面也可
            if (((_a = inputBox) === null || _a === void 0 ? void 0 : _a.value.length) < 2)
                return;
            // 更新指示灯
            search_pointer.textContent = `正在搜索:${inputBox.value}`;
            search_pointer.style.color = pointerColors.loading;
            let requests = [];
            let others = [{ "title": "没有搜索结果,也可以看看:", "href": "#" }];
            for (let s of C.sites) {
                if (s.search !== undefined) {
                    // 搜索开始
                    requests.push(s.search((_b = inputBox) === null || _b === void 0 ? void 0 : _b.value, s.url));
                }
                else {
                    others.push({ "title": s.desc, "href": s.url });
                }
            }
            let result_count = 0, failed = 0;
            let list = await Promise.allSettled(requests);
            // 获取结果后清空旧内容
            search_ul.innerHTML = "";
            for (let site of list) {
                if (site.status === "fulfilled") {
                    for (let lnk of site.value) {
                        let li = document.createElement("li");
                        li.textContent = lnk.title.trim();
                        li.onclick = ev => GM_openInTab(lnk.href, { active: true });
                        search_ul.append(li);
                        result_count++;
                    }
                }
                else {
                    failed++;
                }
            }
            // 处理一下没有结果的情况,把没有实现 search 的网站摆上去
            if (result_count === 0) {
                for (let o of others) {
                    let li = document.createElement("li");
                    li.textContent = o.title;
                    li.onclick = ev => GM_openInTab(o.href, { active: true });
                    search_ul.append(li);
                }
            }
            // 更新指示
            search_pointer.textContent = `搜索完成:${result_count} 条结果 [${failed} 错误]`;
            search_pointer.style.color = pointerColors.loaded;
        }, 1000);
    };
    // 搜索框开关
    function toggleSearch() {
        if (parseInt(searchBox.style.right) < 0) {
            searchBox.style.right = "8px";
        }
        else {
            searchBox.style.right = "-300px";
        }
    }
    /*
      以下是工具函数
     */
    /**
     * 发起请求
     *
     * @param details 油猴标准请求格式,onload,onerror,responseType 会被忽略
     * @param timeout 超时时间 默认:5000
     * @param encoding 请求数据的编码 默认:当前所在页面的编码
     * @returns Promise<Document>
     */
    function rq(details, timeout = 5000, encoding) {
        // 自动探测一手
        if (!encoding)
            encoding = document.characterSet;
        return new Promise((res, rej) => {
            details.onerror = rej;
            details.ontimeout = rej;
            details.timeout = timeout;
            details.responseType = "arraybuffer";
            details.onload = resp => {
                if (resp.status != 200)
                    rej();
                let decoder = new TextDecoder(encoding);
                res(new DOMParser()
                    .parseFromString(decoder.decode(resp.response), "text/html"));
            };
            GM_xmlhttpRequest(details);
        });
    }
    /**
     * 返回符合条件的第一个元素
     *
     * @param doc 被查找的文档
     * @param selector 选择器
     * @param text 可选 元素的文本(子字符串)
     * @returns 符合条件的元素
     */
    function fd(doc, selector, text) {
        var _a;
        if (text) {
            for (let e of doc.querySelectorAll(selector)) {
                if ((_a = e.textContent) === null || _a === void 0 ? void 0 : _a.includes(text)) {
                    return e;
                }
            }
        }
        else {
            return doc.querySelector(selector);
        }
        return null;
    }
    /**
     * 拼接 URL
     *
     * @param host 网站域名
     * @param path 一般是链接之中的相对路径
     * @returns 完整的 URL
     */
    function concatURL(host, path) {
        let url = new URL(host);
        url.pathname = path;
        return url.toString();
    }
    /**
     * 如果是 a 标签,且想要获取字面上的 href,必须使用此方法,不可以用 a.href
     *
     * @param ele 标签名
     * @param attr 属性名
     * @returns 属性值
     */
    function attr(ele, attr) {
        var _a;
        return (_a = ele.getAttribute(attr)) !== null && _a !== void 0 ? _a : "";
    }
    /**
     * 检查当前位置是否处于边界
     *
     * @param bottom 是否检查到达底部,否则检查是否处于顶部
     * @param range 距离底部多少,默认是 0(最底部)
     * @returns boolean
     */
    function chkBoundry(bottom = true, range = 0) {
        let root = document.documentElement;
        let winHeight = window.innerHeight;
        if (bottom) {
            return (root.scrollTop + winHeight + range >= root.scrollHeight);
        }
        else {
            return (root.scrollTop == 0);
        }
    }
})();