去除网站外链跳转安全限制

去除简书、知乎、掘金、CSDN、思否、少数派等网站的外链安全限制,将a标签改为直接跳转

"use strict";
// ==UserScript==
// @name         去除网站外链跳转安全限制
// @namespace    https://raw.githubusercontent.com/Fog3211/tampermonkey/gh-pages/external-link-resolver.js
// @version      1.0.2
// @description  去除简书、知乎、掘金、CSDN、思否、少数派等网站的外链安全限制,将a标签改为直接跳转
// @license      MIT
// @match        https://juejin.cn/*
// @match        https://link.juejin.cn/*
// @match        https://segmentfault.com/*
// @match        https://link.segmentfault.com/*
// @match        https://csdn.net/*
// @match        https://link.csdn.net/*
// @match        https://*.jianshu.com/*
// @match        https://*.zhihu.com/*
// @match        https://link.zhihu.com/*
// @match        https://sspai.com/*
// @grant        GM_xmlhttpRequest
// ==/UserScript==
(function () {
    "use strict";
    // Generic extractTarget function
    const genericExtractTarget = (url, paramName) => {
        const params = new URLSearchParams(url.search);
        return params.get(paramName);
    };
    const siteConfigs = {
        "juejin": {
            directMatch: ["https://link.juejin.cn"],
            extractTarget: async (url) => {
                const target = genericExtractTarget(url, "target");
                if (target) {
                    if (target.startsWith("https://link.juejin.cn")) {
                        return siteConfigs["juejin"].extractTarget(new URL(target));
                    }
                    return decodeURIComponent(target);
                }
                return null;
            }
        },
        "segmentfault": {
            directMatch: ["https://link.segmentfault.com"],
            extractTarget: async (url) => {
                return new Promise((resolve) => {
                    GM_xmlhttpRequest({
                        method: "GET",
                        url: url.href,
                        headers: {
                            "Referer": "https://segmentfault.com",
                        },
                        onload: function (response) {
                            const parser = new DOMParser();
                            const doc = parser.parseFromString(response.responseText, "text/html");
                            const dataUrl = doc.body.getAttribute("data-url");
                            resolve(dataUrl || url.href);
                        },
                        onerror: function () {
                            resolve(url.href);
                        }
                    });
                });
            }
        },
        "csdn": {
            directMatch: ["https://link.csdn.net"],
            targetParam: "target"
        },
        "jianshu": {
            directMatch: ["https://link.jianshu.com", "https://links.jianshu.com"],
            targetParam: "to"
        },
        "zhihu": {
            directMatch: ["https://link.zhihu.com"],
            targetParam: "target"
        },
        "sspai": {
            directMatch: ["https://sspai.com"],
            targetParam: "target"
        }
    };
    async function processRule(config) {
        if (config.directMatch.includes(window.location.origin)) {
            let targetUrl = await getTargetUrl(config, new URL(window.location.href));
            while (targetUrl && new URL(targetUrl).origin === "https://link.juejin.cn") {
                // Keep resolving until we get the final non-Juejin link
                targetUrl = await getTargetUrl(siteConfigs["juejin"], new URL(targetUrl));
            }
            if (targetUrl) {
                window.location.href = targetUrl;
            }
            return;
        }
        await replaceLinks(config);
    }
    async function getTargetUrl(config, url) {
        if (config.targetParam) {
            return genericExtractTarget(url, config.targetParam);
        }
        else if (config.extractTarget) {
            return config.extractTarget(url);
        }
        return null;
    }
    async function replaceLinks(config) {
        const linkSelector = config.directMatch.map(site => `a[href^="${site}"]`).join(", ");
        const links = document.querySelectorAll(linkSelector);
        for (const link of links) {
            const href = link.getAttribute("href");
            if (href) {
                const targetUrl = await getTargetUrl(config, new URL(href));
                if (targetUrl) {
                    link.href = decodeURIComponent(targetUrl);
                    link.target = "_blank";
                    link.rel = "noopener noreferrer";
                }
            }
        }
    }
    function observeDOMChanges(config) {
        const observer = new MutationObserver(() => replaceLinks(config));
        observer.observe(document.body, { childList: true, subtree: true });
    }
    async function init() {
        const currentSite = Object.keys(siteConfigs).find(site => siteConfigs[site].directMatch.includes(window.location.origin));
        if (currentSite) {
            const config = siteConfigs[currentSite];
            await processRule(config);
            observeDOMChanges(config);
        }
    }
    init();
})();