WorkshopEnhance

一個簡單的工作坊網址替換腳本,為網址添加 searchtext=<標題>

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

Advertisement:

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

Advertisement:

// ==UserScript==
// @name WorkshopEnhance
// @version 2026/06/24-Beta
// @author Canaan HS
// @description 一個簡單的工作坊網址替換腳本,為網址添加 searchtext=<標題>
// @description:zh-TW 一個簡單的工作坊網址替換腳本,為網址添加 searchtext=<標題>
// @description:zh-CN 一个简单的工作坊网址替换脚本,为网址添加 searchtext=<标题>
// @description:en A simple workshop URL replacement script that adds searchtext=<title> to the URL

// @license MPL-2.0
// @match *://steamcommunity.com/*
// @icon https://cdn-icons-png.flaticon.com/512/220/220608.png

// @noframes
// @grant window.onurlchange

// @run-at document-start
// @namespace https://greasyfork.org/users/989635
// ==/UserScript==

(() => {
    const app = /^https:\/\/steamcommunity\.com\/app\/\d+/;
    const workshop = /^https:\/\/steamcommunity\.com\/workshop\/browse\/\?appid=\d+/;
    const sharedfiles = /^https:\/\/steamcommunity\.com\/sharedfiles\/filedetails\/\?id=\d+/;
    const myworkshopfiles = /^https:\/\/steamcommunity\.com\/profiles\/\d+\/myworkshopfiles\/?.*$/;

    let jumpMark = false;

    function main(url) {
        if (app.test(url)) {
            waitElem("div[style*='--gap: var(--spacing-9);'] div.Panel", findUri)
        }
        else if (workshop.test(url) || myworkshopfiles.test(url)) {
            waitElem("div[style*='--gap: var(--spacing-5);']", container => {
                waitLoad(container, 300, () => {
                    findUri();
                    waitElem("button[data-accent-color='accent']", buttons => {
                        if (buttons.length === 2) {
                            // 當跳轉標記為 true 時,代表先前觸發過,因此移除先前鍵盤監聽
                            if (jumpMark) window.removeEventListener("keydown", triggerTurnPage);

                            jumpMark = false;
                            window.addEventListener(
                                "keydown",
                                event => triggerTurnPage(event, buttons),
                                { capture: true }
                            );
                        }
                    }, true)
                })
            })
        }
        else if (sharedfiles.test(url)) {
            waitElem(".workshopItemTitle", title => reUri(url, title?.textContent ?? ""));
        }
    };

    function _debounce(func, delay) {
        let timer = null;
        return (...args) => {
            clearTimeout(timer);
            timer = setTimeout(() => {
                func(...args);
            }, delay);
        }
    };

    function findUri() {
        const links = document.querySelectorAll("a[href^='https://steamcommunity.com/sharedfiles/filedetails/?id=']:not([fixed='true'])");
        if (links.length % 2 === 0) {
            for (let i = 0; i < links.length; i += 2) {
                const [rawLink, titleLink] = [links[i], links[i + 1]];
                const titleText = titleLink.textContent.trim();

                if (!titleText) continue;

                reUri(rawLink, titleText);
                reUri(titleLink, titleText);
            }
        }
    };

    function reUri(uri, title) {
        const isElement = uri instanceof Element;
        const Uri = isElement ? uri.href : uri;

        const url = new URL(Uri);
        url.searchParams.set("searchtext", title);

        const newUri = url.href.replace(/\+/g, " ");

        isElement
            ? uri.tagName === "A" ? (uri.href = newUri, uri.setAttribute("fixed", "true")) : null
            : history.replaceState(null, '', newUri);
    };

    const turnPage = (button) => {
        jumpMark = true;
        button.click();
    };
    function triggerTurnPage(event, buttons) {
        const key = event.key;
        if (key === "ArrowLeft" && !jumpMark) {
            turnPage(buttons[0]);
        } else if (key === "ArrowRight" && !jumpMark) {
            turnPage(buttons[1]);
        }
    };

    function waitLoad(container, debounce, run) {
        const observer = new MutationObserver(_debounce(() => {
            observer.disconnect();
            run();
        }, debounce));
        observer.observe(container, { subtree: true, childList: true, attributes: true, characterData: true });
    };

    function waitElem(selector, found, all = false) {
        let timer, idleCallback;

        const query = () => {
            const result = all
                ? document.querySelectorAll(selector)
                : document.querySelector(selector);

            if (all ? result.length > 0 : result) {
                cancelIdleCallback(idleCallback);
                // clearTimeout(timer);
                found(result);
            } else {
                idleCallback = requestIdleCallback(query, { timeout: 5e2 });
            }
        }

        idleCallback = requestIdleCallback(query, { timeout: 5e2 });
        // timer = setTimeout(() => {
        // cancelIdleCallback(idleCallback);
        // }, 2e4);
    };

    function onUrlChange(callback, timeout = 15) {
        let timer;
        let cleaned = false;
        let support_urlchange = false;

        const originalPushState = history.pushState;
        const originalReplaceState = history.replaceState;

        const eventHandler = {
            urlchange: () => trigger('urlchange'),
            popstate: () => trigger('popstate'),
            hashchange: () => trigger('hashchange')
        };

        function trigger(type) {
            clearTimeout(timer);
            if (!support_urlchange && type === 'urlchange') support_urlchange = true;
            timer = setTimeout(() => {
                if (support_urlchange) off(false, true);
                callback({
                    type: type,
                    url: location.href,
                    domain: location.hostname
                });
            }, Math.max(15, timeout));
        };

        function off(all = true, clean = false) {
            if (clean && cleaned) return;

            clearTimeout(timer);
            history.pushState = originalPushState;
            history.replaceState = originalReplaceState;
            window.removeEventListener('popstate', eventHandler.popstate);
            window.removeEventListener('hashchange', eventHandler.hashchange);
            all && window.removeEventListener('urlchange', eventHandler.urlchange);

            cleaned = true;
        };

        window.addEventListener('urlchange', eventHandler.urlchange);
        window.addEventListener('popstate', eventHandler.popstate);
        window.addEventListener('hashchange', eventHandler.hashchange);
        history.pushState = function () {
            originalPushState.apply(this, arguments);
            trigger('pushState');
        };
        history.replaceState = function () {
            originalReplaceState.apply(this, arguments);
            trigger('replacestate');
        };

        return { off };
    };

    // 初次執行
    main(location.href);
    // 監聽網址變化
    onUrlChange(change => {
        main(change.url);
    });
})();