手機版網址重新導向到電腦版

當載入手機版網頁時,若電腦版存在,則自動重新導向到電腦版網址。

目前為 2025-09-16 提交的版本,檢視 最新版本

// ==UserScript==
// @name         手機版網址重新導向到電腦版
// @name:en      Mobile to Desktop URL Redirect
// @name:ja      モバイル版URLからデスクトップ版へのリダイレクト
// @name:de      Umleitung von Mobil-URL zur Desktop-Version
// @name:uk      Перенаправлення URL з мобільної на десктопну версію
// @description  當載入手機版網頁時,若電腦版存在,則自動重新導向到電腦版網址。
// @description:en When a mobile webpage is loaded, automatically redirects to the desktop version if it exists.
// @description:ja モバイル版ウェブページが読み込まれた際、デスクトップ版が存在する場合、自動的にデスクトップ版のURLにリダイレクトします。
// @description:de Wenn eine mobile Webseite geladen wird, wird automatisch zur Desktop-Version umgeleitet, falls diese existiert.
// @description:uk Коли завантажується мобільна веб-сторінка, автоматично перенаправляє на десктопну версію, якщо вона існує。
//
// @author       Max
// @namespace    https://github.com/Max46656
// @license      MPL2.0
//
// @version      1.4.1
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-start
// @icon         https://cdn-icons-png.flaticon.com/512/3559/3559356.png
// ==/UserScript==
// @icon from Smashicons

class DesktopSwitcher {
    constructor() {
        this.url = window.location.href;
        this.hostname = window.location.hostname;
        this.blacklist = Array.isArray(GM_getValue("blacklist", [])) ? GM_getValue("blacklist", []) : [];
        this.customRules = GM_getValue("customRules", {});
        this.mobilePatterns = [
            { regex: /:\/\/m\./, replace: "://" },
            { regex: /\/m\//, replace: "/" },
            { regex: /\.mobile\./, replace: "." },
            { regex: /\/mobile\//, replace: "/" },
            { regex: /\.wap\./, replace: "." },
            { regex: /\?view=mobile(&|$)/, replace: "?" },
            { regex: /&view=mobile/, replace: "" },
            { regex: /\?device=mobile(&|$)/, replace: "?" },
            { regex: /&device=mobile/, replace: "" },
        ];

        this.registerMenu();
        this.switch2Desktop();
    }

    registerMenu() {
        GM_registerMenuCommand(`⭘ 加入黑名單:${this.hostname}`, () => this.addBlacklist());
        GM_registerMenuCommand(`✕ 從黑名單移除:${this.hostname}`, () => this.removeBlacklist());
        GM_registerMenuCommand("? 檢視黑名單", () => this.showBlacklist());
        GM_registerMenuCommand(`⭘ 新增自訂規則:${this.hostname}`, () => this.addCustomRule());
        GM_registerMenuCommand(`✎ 修改自訂規則:${this.hostname}`, () => this.updateCustomRule());
        GM_registerMenuCommand(`✕ 刪除自訂規則:${this.hostname}`, () => this.removeCustomRule());
        GM_registerMenuCommand("? 檢視自訂規則", () => this.showCustomRules());
    }

    addCustomRule() {
        if (this.customRules[this.hostname]) {
            console.error(`規則已存在,請使用「修改自訂規則」進行更新。`);
            return;
        }
        const matchString = prompt(`為 ${this.hostname} 輸入自訂字串 (例如 /m.):`);
        const replaceString = prompt(`輸入替換字串 (例如 /):`);
        if (matchString && replaceString) {
            try {
                this.customRules[this.hostname] = { match: matchString, replace: replaceString };
                GM_setValue("customRules", this.customRules);
                console.log(`已為 ${this.hostname} 新增自訂規則: match=${matchString}, replace=${replaceString}`);
            } catch (error) {
                console.error(`無效的 match: ${error.message}`);
            }
        }
        window.location.reload();
    }

    showCustomRules() {
        let message = "";
        for (const [host, rule] of Object.entries(this.customRules)) {
            message += `${host}: match=${rule.match}, replace=${rule.replace}\n`;
        }
        message = message || "(空)";
        console.log(`目前自訂規則:\n${message}`);
    }

    updateCustomRule() {
        if (!this.customRules[this.hostname]) {
            console.error(`無 ${this.hostname} 的自訂規則,請先新增。`);
            return;
        }
        const currentRule = this.customRules[this.hostname];
        const matchString = prompt(`修改 match (目前: ${currentRule.match}):`, currentRule.match);
        const replaceString = prompt(`修改替換字串 (目前: ${currentRule.replace}):`, currentRule.replace);
        if (matchString && replaceString) {
            try {
                this.customRules[this.hostname] = { regex: matchString, replace: replaceString };
                GM_setValue("customRules", this.customRules);
                console.log(`已更新 ${this.hostname} 的自訂規則: match=${matchString}, replace=${replaceString}`);
            } catch (error) {
                console.error(`無效的 match: ${error.message}`);
            }
        }
        window.location.reload();
    }

    removeCustomRule() {
        if (!this.customRules[this.hostname]) {
            console.error(`無 ${this.hostname} 的自訂規則。`);
            return;
        }
        delete this.customRules[this.hostname];
        GM_setValue("customRules", this.customRules);
        console.log(`已刪除 ${this.hostname} 的自訂規則`);
    }

    addBlacklist() {
        this.updateBlacklist(true, this.hostname, this.getDesktopUrl());
    }

    showBlacklist() {
        const message = this.blacklist.length ? this.blacklist.join("\n") : "(空)";
        console.log(`目前黑名單:\n${message}`);
    }

    updateBlacklist(add, hostname, desktopUrl) {
        if (!desktopUrl || desktopUrl === this.url) {
            if (add && !this.blacklist.includes(hostname)) {
                this.blacklist.push(hostname);
                GM_setValue("blacklist", this.blacklist);
                console.log(`已將 ${hostname} 加入黑名單`);
            } else if (!add) {
                this.blacklist = this.blacklist.filter(domain => domain !== hostname);
                GM_setValue("blacklist", this.blacklist);
                console.log(`已將 ${hostname} 從黑名單移除`);
            } else {
                console.warn(`${hostname} 已在黑名單中`);
            }
            window.location.reload();
            return;
        }

        this.checkDesktopUrl(
            desktopUrl,
            (finalUrl, finalHostname) => {
                if (add && !this.blacklist.includes(finalHostname)) {
                    this.blacklist.push(finalHostname);
                    GM_setValue("blacklist", this.blacklist);
                    console.log(`已將 ${finalHostname} 加入黑名單`);
                } else if (!add) {
                    this.blacklist = this.blacklist.filter(domain => domain !== finalHostname);
                    GM_setValue("blacklist", this.blacklist);
                    console.log(`已將 ${finalHostname} 從黑名單移除`);
                } else {
                    console.warn(`${finalHostname} 已在黑名單中`);
                }
            },
            (errorMessage) => {
                console.error(`無法${add ? "加入" : "移除"}黑名單: ${errorMessage}`);
            }
        );
        window.location.reload();
    }

    removeBlacklist() {
        this.updateBlacklist(false, this.hostname, this.getDesktopUrl());
        window.location.reload();
    }

    checkDesktopUrl(desktopUrl, onSuccess, onFailure) {
        GM_xmlhttpRequest({
            method: "HEAD",
            url: desktopUrl,
            timeout: 3000,
            onload: (response) => {
                if (response.status >= 200 && response.status < 400) {
                    const finalUrl = response.finalUrl || desktopUrl;
                    try {
                        const urlObj = new URL(finalUrl);
                        const finalHostname = urlObj.hostname;
                        onSuccess(finalUrl, finalHostname);
                    } catch (error) {
                        console.error(`無效的 URL: ${finalUrl}, 錯誤: ${error.message}`);
                        onFailure(`無效的 URL 格式: ${finalUrl}`);
                    }
                } else {
                    console.error(`請求失敗,狀態碼: ${response.status}, URL: ${desktopUrl}`);
                    onFailure(`無法訪問電腦版,狀態碼: ${response.status}`);
                }
            },
            onerror: () => {
                console.error(`網絡錯誤,URL: ${desktopUrl}`);
                onFailure("網絡錯誤,無法完成請求");
            },
            ontimeout: () => {
                console.error(`請求超時,URL: ${desktopUrl}`);
                onFailure("請求超時,無法訪問電腦版");
            }
        });
    }

    getDesktopUrl() {
        let tempUrl = this.url;
        const customPattern = this.customRules[this.hostname];
        if (customPattern) {
            console.log(`應用自訂規則於: ${this.hostname}, regex=${customPattern.regex}`);
            try {
                tempUrl = tempUrl.replace(customPattern.regex, customPattern.replace);
                if (tempUrl !== this.url) return tempUrl;
            } catch (error) {
                console.error(`應用自訂規則失敗: ${error.message}`);
            }
        }else{
            for (const pattern of this.mobilePatterns) {
                if (pattern.regex.test(tempUrl)) {
                    console.log(`檢測到手機版模式: ${pattern.regex}`);
                    tempUrl = tempUrl.replace(pattern.regex, pattern.replace);
                }
            }
        }
        return tempUrl !== this.url ? tempUrl : null;
    }

    switch2Desktop() {
        const desktopUrl = this.getDesktopUrl();
        if (desktopUrl === null) return

        console.log(`嘗試解析 canonical tag: ${this.url}`);
        GM_xmlhttpRequest({
            method: "GET",
            url: this.url,
            timeout: 3000,
            onload: (response) => {
                if (response.status >= 200 && response.status < 400) {
                    const parser = new DOMParser();
                    const doc = parser.parseFromString(response.responseText, "text/html");
                    const canonical = doc.querySelector('link[rel="canonical"]');
                    if (canonical && canonical.href && canonical.href !== this.url) {
                        try {
                            const canonicalHostname = new URL(canonical.href).hostname;
                            if (this.blacklist.includes(canonicalHostname)) {
                                console.warn(`阻止重新導向,黑名單域名: ${canonicalHostname}`);
                                return;
                            }
                            console.log(`找到 canonical URL: ${canonical.href}`);
                            window.location.replace(canonical.href);
                            return;
                        } catch (error) {
                            console.error(`無效的 canonical URL: ${canonical.href}, 錯誤: ${error.message}`);
                        }
                    } else {
                        console.warn(`未找到有效 canonical tag,嘗試模式符合`);
                    }
                } else {
                    console.error(`無法載入頁面內容: ${response.status}`);
                }
                tryPatternMatch();
            },
            onerror: () => {
                console.error(`網絡錯誤: ${this.url}`);
                tryPatternMatch();
            },
            ontimeout: () => {
                console.error(`請求超時: ${this.url}`);
                tryPatternMatch();
            }
        });

        const tryPatternMatch = () => {
                console.log(`嘗試切換到電腦版網址: ${desktopUrl}`);
                this.checkDesktopUrl(
                    desktopUrl,
                    (finalUrl, finalHostname) => {
                        if (this.blacklist.includes(finalHostname)) {
                            console.warn(`阻止重新導向,黑名單域名: ${finalHostname}`);
                            return;
                        }
                        console.log(`正在重新導向到電腦版: ${finalUrl}`);
                        window.location.replace(finalUrl);
                    },
                    (errorMessage) => {
                        console.error(`無法切換到電腦版: ${errorMessage}`);
                    }
                );
        };
    }

}

new DesktopSwitcher();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址