去除链接重定向

能原地解析的链接绝不在后台访问,去除重定向的过程快速且高效,平均时间在0.02ms~0.05ms之间。几乎没有任何在后台访问网页获取去重链接的操作,一切都在原地进行,对速度精益求精。去除网页内链接的重定向,具有高准确性和高稳定性,以及相比同类插件更低的时间占用。并且保证去除重定向的有效性,采用三级方案,原地解析->自动跳转->后台访问,保证了一定能去除重定向链接

// ==UserScript==
// @name              去除链接重定向
// @author            Meriel
// @description       能原地解析的链接绝不在后台访问,去除重定向的过程快速且高效,平均时间在0.02ms~0.05ms之间。几乎没有任何在后台访问网页获取去重链接的操作,一切都在原地进行,对速度精益求精。去除网页内链接的重定向,具有高准确性和高稳定性,以及相比同类插件更低的时间占用。并且保证去除重定向的有效性,采用三级方案,原地解析->自动跳转->后台访问,保证了一定能去除重定向链接
// @version           2.7.8
// @namespace         Violentmonkey Scripts
// @grant             GM.xmlHttpRequest
// @match             *://*/*
// @connect           *
// @icon              https://cdn-icons-png.flaticon.com/512/208/208895.png
// @supportURL        https://github.com/MerielVaren/remove-link-redirects
// @homepage          https://greasyfork.org/zh-CN/scripts/483475-%E5%8E%BB%E9%99%A4%E9%93%BE%E6%8E%A5%E9%87%8D%E5%AE%9A%E5%90%91
// @run-at            document-end
// @namespace         https://greasyfork.org/zh-CN/users/876245-meriel-varen
// @license           MIT
// ==/UserScript==

(() => {
  /********** 以下为自动跳转部分 **********/
  class AutoJumpApp {
    constructor() {
      this.registeredProvider = void 0;
    }

    /**
     * 注册服务提供者
     * @param providers
     */
    registerProvider() {
      for (const provider of AutoJumpApp.providers) {
        if (
          provider.urlTest instanceof RegExp &&
          !provider.urlTest.test(location.href)
        ) {
          continue;
        }
        if (provider.urlTest === false) {
          continue;
        }
        if (typeof provider.urlTest === "function" && !provider.urlTest()) {
          continue;
        }
        this.registeredProvider = provider;
        break;
      }
      return this;
    }

    /**
     * 启动应用
     * @returns
     * */
    bootstrap() {
      this.registerProvider();
      if (this.registeredProvider) {
        this.registeredProvider.resolveAutoJump();
        return true;
      }
      return false;
    }

    static providers = [
      {
        name: "酷安",
        urlTest: /www\.coolapk\.com\/link\?.*url=(.*)/,
        resolveAutoJump: function () {
          location.href = decodeURIComponent(
            new URL(location.href).searchParams.get("url")
          );
        },
      },
      {
        name: "CSDN",
        urlTest: /link\.csdn\.net\/\?.*target=(.*)/,
        resolveAutoJump: function () {
          location.href = decodeURIComponent(
            new URL(location.href).searchParams.get("target")
          );
        },
      },
      {
        name: "腾讯兔小巢",
        urlTest: /support\.qq\.com\/.*link-jump\?jump=(.*)/,
        resolveAutoJump: function () {
          location.href = decodeURIComponent(
            new URL(location.href).searchParams.get("jump")
          );
        },
      },
      {
        name: "QQ邮箱",
        urlTest: /mail\.qq\.com\/.*gourl=(.*)/,
        resolveAutoJump: function () {
          location.href = decodeURIComponent(
            new URL(location.href).searchParams.get("gourl")
          );
        },
      },
      {
        name: "印象笔记",
        urlTest: /app\.yinxiang\.com\/OutboundRedirect\.action\?.*dest=(.*)/,
        resolveAutoJump: function () {
          location.href = decodeURIComponent(
            new URL(location.href).searchParams.get("dest")
          );
        },
      },
      {
        name: "Youtube",
        urlTest: /www\.youtube\.com\/redirect\?.*q=(.*)/,
        resolveAutoJump: function () {
          location.href = decodeURIComponent(
            new URL(location.href).searchParams.get("q")
          );
        },
      },
      {
        name: "微信开放社区",
        urlTest: /developers\.weixin\.qq\.com\/.*href=(.*)/,
        resolveAutoJump: function () {
          location.href = decodeURIComponent(
            new URL(location.href).searchParams.get("href")
          );
        },
      },
      {
        name: "pc6下载站",
        urlTest: /www\.pc6\.com\/.*gourl=(.*)/,
        resolveAutoJump: function () {
          location.href = decodeURIComponent(
            new URL(location.href).searchParams.get("gourl")
          );
        },
      },
      {
        name: "51CTO博客",
        urlTest: /blog\.51cto\.com\/transfer\?(.*)/,
        resolveAutoJump: function () {
          location.href = decodeURIComponent(
            this.urlTest.exec(location.href)[1]
          );
        },
      },
      {
        name: "QQ",
        urlTest: /c\.pc\.qq\.com.*pfurl=(.*)/,
        resolveAutoJump: function () {
          location.href = decodeURIComponent(
            new URL(location.href).searchParams.get("pfurl")
          );
        },
      },
      {
        name: "QQ",
        urlTest: /c\.pc\.qq\.com.*url=(.*)/,
        resolveAutoJump: function () {
          location.href = decodeURIComponent(
            new URL(location.href).searchParams.get("url")
          );
        },
      },
      {
        name: "UrlShare",
        urlTest: /.+\.urlshare\..+\/.*url=(.*)/,
        resolveAutoJump: function () {
          location.href = decodeURIComponent(
            new URL(location.href).searchParams.get("url")
          );
        },
      },
      {
        name: "腾讯文档",
        urlTest: /docs\.qq\.com\/.*\?url=(.*)/,
        resolveAutoJump: function () {
          location.href = decodeURIComponent(
            new URL(location.href).searchParams.get("url")
          );
        },
      },
      {
        name: "金山文档",
        urlTest: /www\.kdocs\.cn\/office\/link\?.*target=(.*)/,
        resolveAutoJump: function () {
          location.href = decodeURIComponent(
            new URL(location.href).searchParams.get("target")
          );
        },
      },
      {
        name: "NodeSeek",
        urlTest: /www\.nodeseek\.com\/jump\?.*to=(.*)/,
        resolveAutoJump: function () {
          location.href = decodeURIComponent(
            new URL(location.href).searchParams.get("to")
          );
        },
      },
      {
        name: "新版QQ邮箱",
        urlTest: /wx\.mail\.qq\.com\/xmspamcheck\/xmsafejump\?/,
        resolveAutoJump: function () {
          location.href = decodeURIComponent(
            new URL(location.href).searchParams.get("url")
          );
        },
      },
    ];
  }

  /********** 以下为重定向解析部分 **********/
  class RedirectApp {
    /**
     * 调节providers的顺序
     * 将匹配到的provider放到最前
     * @param provider
     */
    adjustProviderOrderOnce = (function () {
      let executed = false; // 标志变量,用于跟踪函数是否已执行
      return function (provider) {
        if (!executed) {
          const index = this.registeredProviders.indexOf(provider);
          if (index !== -1) {
            this.registeredProviders.splice(index, 1);
            this.registeredProviders.unshift(provider);
          }
          executed = true;
        }
      };
    })();

    /**
     * A 标签是否匹配服务提供者
     * @param element
     * @param provider
     */
    static isMatchProvider(element, provider) {
      if (element.getAttribute(RedirectApp.REDIRECT_COMPLETED)) {
        return false;
      }
      if (
        provider.linkTest instanceof RegExp &&
        !provider.linkTest.test(element.href)
      ) {
        return false;
      }
      if (provider.linkTest instanceof Boolean) {
        return provider.linkTest;
      }
      if (
        typeof provider.linkTest === "function" &&
        !provider.linkTest(element)
      ) {
        return false;
      }
      return true;
    }

    /**
     * 解析完成的标志
     */
    static REDIRECT_COMPLETED = "redirect-completed";

    /**
     * 兜底解析器
     * 用于解析无法解析的链接
     * 通过GM.xmlHttpRequest获取最终链接
     */
    static FallbackResolver = class {
      constructor() {
        this.processedUrls = new Map();
      }

      async resolveRedirect(element) {
        const href = element.href;

        if (!this.processedUrls.has(href)) {
          // 创建一个新的 Promise 并存储在 Map 中
          let resolvePromise;
          const promise = new Promise((resolve) => {
            resolvePromise = resolve;
          });
          this.processedUrls.set(href, promise);

          try {
            const res = await GM.xmlHttpRequest({
              method: "GET",
              url: href,
              anonymous: true,
            });
            if (res.finalUrl) {
              const url = res.finalUrl;
              this.processedUrls.set(href, url);
              element.href = url;
            } else {
              this.processedUrls.delete(href); // 请求失败时删除占位符
            }
          } catch (error) {
            console.error("请求失败:", error);
            this.processedUrls.delete(href); // 请求失败时删除占位符
          } finally {
            resolvePromise(); // 请求完成后解析 Promise
          }
        } else {
          const cachedValue = this.processedUrls.get(href);

          if (cachedValue instanceof Promise) {
            // 如果是 Promise,等待其完成
            await cachedValue;
            element.href = this.processedUrls.get(href);
          } else {
            // 否则直接使用缓存值
            element.href = cachedValue;
          }
        }
      }
    };

    /**
     * 移除链接重定向
     * 首先判断是否可以直接解析链接,如果可以则直接解析
     * 如果不行,则调用fallbackResolver解析
     * @param caller 调用者
     * @param element 链接元素
     * @param realUrl 真实链接
     * @param options 配置项
     * @returns
     * */
    static removeLinkRedirect(caller, element, realUrl, options) {
      element.setAttribute(RedirectApp.REDIRECT_COMPLETED, "true");
      if ((realUrl && element.href !== realUrl) || options?.force) {
        try {
          element.href = decodeURIComponent(realUrl);
        } catch (_) {
          element.href = realUrl;
        }
      } else if (caller) {
        if (!caller.fallbackResolver) {
          caller.fallbackResolver = new RedirectApp.FallbackResolver();
        }
        caller.fallbackResolver.resolveRedirect(element);
      }
    }

    /**
     * 监听URL变化
     * @param operation
     * @returns
     * */
    static monitorUrlChange(operation) {
      function urlChange(event) {
        const destinationUrl = event?.destination?.url || "";
        if (destinationUrl.startsWith("about:blank")) return;
        const href = destinationUrl || location.href;
        if (href !== location.href) {
          operation(href);
        }
      }
      unsafeWindow?.navigation?.addEventListener("navigate", urlChange);
      unsafeWindow.addEventListener("replaceState", urlChange);
      unsafeWindow.addEventListener("pushState", urlChange);
      unsafeWindow.addEventListener("popState", urlChange);
      unsafeWindow.addEventListener("hashchange", urlChange);
    }

    constructor() {
      this.registeredProviders = [];
      this.mutationObserver = new MutationObserver((mutations) => {
        mutations.forEach(this.handleMutation.bind(this));
      });
    }

    /**
     * 处理变动
     * @param mutation
     * @returns
     * */
    handleMutation(mutation) {
      if (mutation.type === "childList") {
        mutation.addedNodes.forEach((node) => {
          if (node instanceof HTMLAnchorElement) {
            this.handleNode(node);
          } else {
            // 有些网站被observer观察到的是一个div,里面包含了很多a标签
            // 这种情况下,需要对所有的a标签进行处理
            node
              ?.querySelectorAll?.(`a:not([${RedirectApp.REDIRECT_COMPLETED}])`)
              ?.forEach((aNode) => this.handleNode(aNode));
          }
        });
      }
    }

    /**
     * 处理节点
     * @param node
     * @returns
     */
    handleNode(node) {
      for (const provider of this.registeredProviders) {
        if (RedirectApp.isMatchProvider(node, provider)) {
          provider.resolveRedirect(node);
          this.adjustProviderOrderOnce(provider);
          break;
        }
      }
    }

    /**
     * 当页面准备就绪时,进行初始化动作
     * 有一些服务提供者需要在页面准备就绪时进行特殊的初始化操作
     * 比如百度搜索,需要监听URL变化
     * 以及一些情况不需要RediectApp介入
     * 如谷歌搜索需要监听的是href变化,而链接本身没有重定向
     */
    async initProviders() {
      for (const provider of this.registeredProviders) {
        if (provider.onInit) {
          await provider.onInit();
        }
      }
    }

    /**
     * 注册服务提供者
     * @param providers
     */
    registerProviders() {
      for (const provider of RedirectApp.providers) {
        if (provider.urlTest === false) {
          continue;
        }
        if (
          provider.urlTest instanceof RegExp &&
          !provider.urlTest.test(location.href)
        ) {
          continue;
        }
        if (typeof provider.urlTest === "function" && !provider.urlTest()) {
          continue;
        }
        this.registeredProviders.push(provider);
      }
      return this;
    }

    /**
     * 启动应用
     */
    bootstrap() {
      this.registerProviders();
      document.querySelectorAll("a").forEach((element) => {
        this.handleNode(element);
      });
      console.log("去除重定向服务正在运行:", this.registeredProviders);
      addEventListener("DOMContentLoaded", this.initProviders.bind(this));
      this.mutationObserver.observe(document, {
        childList: true,
        subtree: true,
      });
    }

    static providers = [
      {
        name: "如有乐享",
        urlTest: /51\.ruyo\.net/,
        linkTest: /\/[^\?]*\?u=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("u")
          );
        },
      },
      {
        name: "Mozilla",
        urlTest: /addons\.mozilla\.org/,
        linkTest: /outgoing\.prod\.mozaws\.net\/v\d\/\w+\/(.*)/,
        resolveRedirect: function (element) {
          let url = void 0;
          const match = this.linkTest.exec(element.href);
          if (match && match[1]) {
            try {
              url = decodeURIComponent(match[1]);
            } catch (_) {
              url = /(http|https)?:\/\//.test(match[1]) ? match[1] : void 0;
            }
          }
          RedirectApp.removeLinkRedirect(this, element, url);
        },
      },
      {
        name: "爱发电",
        urlTest: /afdian\.net/,
        linkTest: /afdian\.net\/link\?target=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("target")
          );
        },
      },
      {
        name: "印象笔记",
        urlTest: /(www|app)\.yinxiang\.com/,
        linkTest: true,
        resolveRedirect: function (element) {
          if (element.hasAttribute("data-mce-href")) {
            if (!element.onclick) {
              RedirectApp.removeLinkRedirect(this, element, element.href, {
                force: true,
              });
              element.onclick = function (e) {
                // 阻止事件冒泡, 因为上层元素绑定的click事件会重定向
                e.stopPropagation?.();
                element.setAttribute("target", "_blank");
                window.top
                  ? window.top.open(element.href)
                  : window.open(element.href);
              };
            }
          }
        },
        onInit: async function () {
          const handler = function (e) {
            const dom = e.target;
            const tagName = dom.tagName.toUpperCase();
            switch (tagName) {
              case "A": {
                this.resolveRedirect(dom);
                break;
              }
              case "IFRAME": {
                if (dom.hasAttribute("redirect-link-removed")) {
                  return;
                }
                dom.setAttribute("redirect-link-removed", "true");
                dom.contentWindow.document.addEventListener(
                  "mouseover",
                  handler
                );
                break;
              }
            }
          };
          document.addEventListener("mouseover", handler);
        },
      },
      {
        name: "印象笔记",
        urlTest: /app\.yinxiang\.com/,
        linkTest:
          /(www|app)\.yinxiang\.com\/OutboundRedirect\.action\?dest=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("dest")
          );
        },
      },
      {
        name: "Bing",
        urlTest: /bing\.com/,
        linkTest: /.+\.bing\.com\/ck\/a\?.*&u=a1(.*)&ntb=1/,
        textDecoder: new TextDecoder("utf-8"),
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            this.textDecoder.decode(
              Uint8Array.from(
                Array.from(
                  atob(
                    element.href
                      .split("&u=a1")[1]
                      .split("&ntb=1")[0]
                      .replace(/[-_]/g, (e) => ("-" === e ? "+" : "/"))
                      .replace(/[^A-Za-z0-9\\+\\/]/g, "")
                  )
                ).map((e) => e.charCodeAt(0))
              )
            )
          );
        },
      },
      {
        name: "51CTO博客",
        urlTest: /blog\.51cto\.com/,
        linkTest: true,
        resolveRedirect: function (element) {
          const container = document.querySelector(".article-detail");
          if (container?.contains(element)) {
            if (!element.onclick && element.href) {
              element.onclick = function (e) {
                e.stopPropagation?.();
                const $a = document.createElement("a");
                $a.href = element.href;
                $a.target = element.target;
                $a.click();
              };
            }
          }
        },
      },
      {
        name: "51CTO博客",
        urlTest: /blog\.51cto\.com/,
        linkTest: /blog\.51cto\.com\/.*transfer\?(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("url")
          );
        },
      },
      {
        name: "CSDN",
        urlTest: /blog\.csdn\.net/,
        linkTest: true,
        resolveRedirect: function (element) {
          const container = document.querySelector("#content_views");
          if (!container?.contains(element)) {
            return;
          }

          if (!element.onclick && element.origin !== window.location.origin) {
            RedirectApp.removeLinkRedirect(this, element, element.href, {
              force: true,
            });
            element.onclick = function (e) {
              // 阻止事件冒泡, 因为上层元素绑定的click事件会重定向
              e.stopPropagation?.();
              e.preventDefault?.();
              e.stopImmediatePropagation?.();
            };
          }
        },
      },
      {
        name: "知乎日报",
        urlTest: /daily\.zhihu\.com/,
        linkTest: /zhihu\.com\/\?target=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("target")
          );
        },
      },
      {
        name: "Google Docs",
        urlTest: /docs\.google\.com/,
        linkTest: /www\.google\.com\/url\?q=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("q")
          );
        },
      },
      {
        name: "Pocket",
        urlTest: /getpocket\.com/,
        linkTest: /getpocket\.com\/redirect\?url=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("url")
          );
        },
      },
      {
        name: "Gitee",
        urlTest: /gitee\.com/,
        linkTest: /gitee\.com\/link\?target=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("target")
          );
        },
      },
      {
        name: "InfoQ",
        urlTest: /infoq\.cn/,
        linkTest: /infoq\.cn\/link\?target=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("target")
          );
        },
      },
      {
        name: "掘金",
        urlTest: /juejin\.(im|cn)/,
        linkTest: /link\.juejin\.(im|cn)\/\?target=(.*)/,
        resolveRedirect: function (element) {
          const finalURL = new URL(element.href).searchParams.get("target");
          RedirectApp.removeLinkRedirect(this, element, finalURL);
          if (this.linkTest.test(element.title)) {
            element.title = finalURL;
          }
        },
      },
      {
        name: "QQ邮箱",
        urlTest: /mail\.qq\.com/,
        linkTest: true,
        resolveRedirect: function (element) {
          const container = document.querySelector("#contentDiv");
          if (container?.contains(element)) {
            if (!element.onclick) {
              element.onclick = function (e) {
                // 阻止事件冒泡, 因为上层元素绑定的click事件会重定向
                e.stopPropagation?.();
              };
            }
          }
        },
      },
      {
        name: "QQ邮箱",
        urlTest: /mail\.qq\.com/,
        linkTest: /mail\.qq\.com.+gourl=(.+).*/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("gourl")
          );
        },
      },
      {
        name: "新版QQ邮箱",
        urlTest: () => {
          if (
            /mail\.qq\.com/.test(location.href) ||
            /wx\.mail\.qq\.com/.test(location.href)
          ) {
            return true;
          }
          return false;
        },
        linkTest: /wx\.mail\.qq\.com\/xmspamcheck\/xmsafejump\?/,
        resolveRedirect: function (element) {
          const url = new URL(element.href).searchParams.get("url");
          RedirectApp.removeLinkRedirect(this, element, url);
        },
      },
      {
        name: "OS China",
        urlTest: /oschina\.net/,
        linkTest: /oschina\.net\/action\/GoToLink\?url=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("url")
          );
        },
      },
      {
        name: "Google Play",
        urlTest: /play\.google\.com/,
        linkTest: function (element) {
          if (/google\.com\/url\?q=(.*)/.test(element.href)) {
            return true;
          } else if (/^\/store\/apps\/details/.test(location.pathname)) {
            return true;
          }
          return false;
        },
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("q")
          );
          const eles = [].slice.call(document.querySelectorAll("a.hrTbp"));
          for (const ele of eles) {
            if (!ele.href || ele.getAttribute(RedirectApp.REDIRECT_COMPLETED)) {
              continue;
            }
            ele.setAttribute(RedirectApp.REDIRECT_COMPLETED, "true");
            ele.setAttribute("target", "_blank");
            ele.addEventListener(
              "click",
              (event) => {
                event.stopPropagation();
              },
              true
            );
          }
        },
      },
      {
        name: "少数派",
        urlTest: /sspai\.com/,
        linkTest: /sspai\.com\/link\?target=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("target")
          );
        },
      },
      {
        name: "Steam Community",
        urlTest: /steamcommunity\.com/,
        linkTest: /steamcommunity\.com\/linkfilter\/\?url=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("url")
          );
        },
      },
      {
        name: "百度贴吧",
        urlTest: /tieba\.baidu\.com/,
        linkTest: /jump\d*\.bdimg\.com/,
        resolveRedirect: function (element) {
          let url = void 0;
          const text = element.innerText || element.textContent || void 0;
          const isUrl = /(http|https)?:\/\//.test(text);
          if (isUrl) {
            try {
              url = decodeURIComponent(text);
            } catch (_) {
              url = text;
            }
          }
          RedirectApp.removeLinkRedirect(this, element, url);
        },
      },
      {
        name: "Twitter",
        urlTest: /(twitter|x)\.com/,
        linkTest: /t\.co\/\w+/,
        resolveRedirect: async function (element) {
          if (/(http|https)?:\/\//.test(element.title)) {
            const url = decodeURIComponent(element.title);
            RedirectApp.removeLinkRedirect(this, element, url);
            return;
          }
          const textContent = element.textContent.replace(/…$/, "");
          if (/(http|https)?:\/\//.test(textContent)) {
            RedirectApp.removeLinkRedirect(this, element, textContent);
            return;
          } else {
            const res = await GM.xmlHttpRequest({
              method: "GET",
              url: "https://" + textContent,
              anonymous: true,
            });
            if (res.status === 200) {
              RedirectApp.removeLinkRedirect(this, element, res.finalUrl);
            } else {
              RedirectApp.removeLinkRedirect(
                this,
                element,
                "http://" + textContent
              );
            }
          }
        },
      },
      {
        name: "微博",
        urlTest: /\.weibo\.(com|cn)/,
        linkTest: /t\.cn\/\w+/,
        resolveRedirect: function (element) {
          if (!/^(http|https)?:\/\//.test(element.title)) {
            return;
          }
          let url = void 0;
          try {
            url = decodeURIComponent(element.title);
          } catch (_) {}
          RedirectApp.removeLinkRedirect(this, element, url);
        },
      },
      {
        name: "微博",
        urlTest: /weibo\.(com|cn)/,
        linkTest: /weibo\.(com|cn)\/sinaurl\?u=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            decodeURIComponent(new URL(element.href).searchParams.get("u"))
          );
        },
      },
      {
        name: "百度搜索",
        urlTest: /www\.baidu\.com/,
        linkTest: /www\.baidu\.com\/link\?url=/,
        unresolvableWebsites: ["nourl.ubs.baidu.com", "lightapp.baidu.com"],
        specialElements: [
          ".cos-row",
          ".c-group-wrapper",
          ".subLink_answer",
          ".subLink_answer ~ a",
          "[class*=catalog-list]",
          "[class*=group-content]",
        ],
        fallbackResolver: new RedirectApp.FallbackResolver(),
        resolveRedirect: async function (element) {
          const url = this.specialElements.some((selector) =>
            element.closest(selector)
          )
            ? void 0
            : element.closest(".c-container[mu]")?.getAttribute("mu");
          if (
            url &&
            url !== "null" &&
            url !== "undefined" &&
            url !== "" &&
            url !== "about:blank" &&
            url !== "javascript:void(0);" &&
            !this.unresolvableWebsites.some((u) => url?.includes(u))
          ) {
            RedirectApp.removeLinkRedirect(this, element, url);
          } else {
            this.fallbackResolver.resolveRedirect(element);
          }
        },
        onInit: async function () {
          RedirectApp.monitorUrlChange((href) => {
            const url = new URL(location.href);
            if (url.searchParams.has("wd")) {
              location.href = href;
            }
          });
        },
      },
      {
        name: "豆瓣",
        urlTest: /douban\.com/,
        linkTest: /douban\.com\/link2\/?\?url=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("url")
          );
        },
      },
      {
        name: "Google搜索",
        urlTest: /\w+\.google\./,
        linkTest: true,
        resolveRedirect: function (element) {
          const traceProperties = [
            "ping",
            "data-jsarwt",
            "data-usg",
            "data-ved",
          ];
          // 移除追踪
          for (const property of traceProperties) {
            if (element.getAttribute(property)) {
              element.removeAttribute(property);
            }
          }
          // 移除多余的事件
          if (element.getAttribute("onmousedown")) {
            element.removeAttribute("onmousedown");
          }
          // 尝试去除重定向
          if (element.getAttribute("data-href")) {
            const realUrl = element.getAttribute("data-href");
            RedirectApp.removeLinkRedirect(this, element, realUrl);
          }
          if (element && element.href) {
            const url = new URL(element?.href);
            if (url?.searchParams.get("url")) {
              RedirectApp.removeLinkRedirect(
                this,
                element,
                url.searchParams.get("url")
              );
            }
          }
        },
      },
      {
        name: "简书",
        urlTest: /www\.jianshu\.com/,
        linkTest: function (element) {
          const isLink1 = /links\.jianshu\.com\/go/.test(element.href);
          const isLink2 = /link\.jianshu\.com(\/)?\?t=/.test(element.href);
          const isLink3 = /jianshu\.com\/go-wild\/?\?(.*)url=/.test(
            element.href
          );
          if (isLink1 || isLink2 || isLink3) {
            return true;
          }
          return false;
        },
        resolveRedirect: function (element) {
          const search = new URL(element.href).searchParams;
          RedirectApp.removeLinkRedirect(
            this,
            element,
            search.get("to") || search.get("t") || search.get("url")
          );
        },
        onInit: async function () {
          document
            .querySelectorAll(`a:not[${RedirectApp.REDIRECT_COMPLETED}]`)
            .forEach((element) => {
              if (this.linkTest(element)) {
                this.resolveRedirect(element);
              }
            });
        },
      },
      {
        name: "标志情报局",
        urlTest: /www\.logonews\.cn/,
        linkTest: /link\.logonews\.cn\/\?url=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("url")
          );
        },
      },
      {
        name: "360搜索",
        urlTest: /www\.so\.com/,
        linkTest: /so\.com\/link\?(.*)/,
        resolveRedirect: function (element) {
          const url =
            element.getAttribute("data-mdurl") ||
            element.getAttribute("e-landurl");
          if (url) {
            RedirectApp.removeLinkRedirect(this, element, url);
          }
          // remove track
          element.removeAttribute("e_href");
          element.removeAttribute("data-res");
        },
      },
      {
        name: "搜狗搜索",
        urlTest: /www\.sogou\.com/,
        linkTest: /www\.sogou\.com\/link\?url=/,
        resolveRedirect: function (element) {
          const vrwrap = element.closest(".vrwrap");
          const rSech = vrwrap.querySelector(".r-sech[data-url]");
          const url = rSech.getAttribute("data-url");
          RedirectApp.removeLinkRedirect(this, element, url);
        },
      },
      {
        name: "Youtube",
        urlTest: /www\.youtube\.com/,
        linkTest: /www\.youtube\.com\/redirect\?.{1,}/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("q")
          );
        },
      },
      {
        name: "知乎",
        urlTest: /www\.zhihu\.com/,
        linkTest: /zhihu\.com\/\?target=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("target")
          );
        },
      },
      {
        name: "百度学术",
        urlTest: /xueshu\.baidu\.com/,
        linkTest: /xueshu\.baidu\.com\/s?\?(.*)/,
        resolveRedirect: function (element) {
          const url =
            element.getAttribute("data-link") ||
            element.getAttribute("data-url") ||
            void 0;
          RedirectApp.removeLinkRedirect(
            this,
            element,
            decodeURIComponent(url)
          );
        },
      },
      {
        name: "知乎专栏",
        urlTest: /zhuanlan\.zhihu\.com/,
        linkTest: /link\.zhihu\.com\/\?target=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("target")
          );
        },
      },
      {
        name: "力扣",
        urlTest: /leetcode\.(cn|com)/,
        linkTest: /leetcode\.(cn|com)\/link\?target=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("target")
          );
        },
      },
      {
        name: "腾讯开发者社区",
        urlTest: /cloud\.tencent\.com/,
        linkTest:
          /cloud\.tencent\.com\/developer\/tools\/blog-entry\?target=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("target")
          );
        },
      },
      {
        name: "酷安",
        urlTest: true,
        linkTest: /www\.coolapk\.com\/link\?url=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("url")
          );
        },
      },
      {
        name: "腾讯兔小巢",
        urlTest: /support\.qq\.com/,
        linkTest: /support\.qq\.com\/.*link-jump\?jump=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("jump")
          );
        },
      },
      {
        name: "微信开放社区",
        urlTest: /developers\.weixin\.qq\.com/,
        linkTest: /developers\.weixin\.qq\.com\/.*href=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("href")
          );
        },
      },
      {
        name: "pc6下载站",
        urlTest: /www\.pc6\.com/,
        linkTest: /www\.pc6\.com\/.*\?gourl=(.*)/,
        customDecode: function (encoded) {
          const key = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
          const len = key.length;
          let d = 0;
          let s = new Array(Math.floor(encoded.length / 3));
          const b = s.length;
          for (let i = 0; i < b; i++) {
            const b1 = key.indexOf(encoded.charAt(d++));
            const b2 = key.indexOf(encoded.charAt(d++));
            const b3 = key.indexOf(encoded.charAt(d++));
            s[i] = b1 * len * len + b2 * len + b3;
          }
          const decoded = String.fromCharCode(...s);
          return decoded;
        },
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            this.customDecode(new URL(element.href).searchParams.get("gourl"))
          );
        },
      },
      {
        name: "QQ",
        urlTest: true,
        linkTest: /c\.pc\.qq\.com.*pfurl=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("pfurl")
          );
        },
      },
      {
        name: "QQ",
        urlTest: true,
        linkTest: /c\.pc\.qq\.com.*url=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            decodeURIComponent(new URL(element.href).searchParams.get("url"))
          );
        },
      },
      {
        name: "UrlShare",
        urlTest: true,
        linkTest: /.+\.urlshare\..+\/.*url=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            decodeURIComponent(new URL(element.href).searchParams.get("url"))
          );
        },
      },
      {
        name: "PHP中文网",
        urlTest: /www\.php\.cn/,
        linkTest: /www\.php\.cn\/link(.*)/,
        resolveRedirect: async function (element) {
          const res = await GM.xmlHttpRequest({
            method: "GET",
            url: element.href,
            anonymous: true,
          });
          const parser = new DOMParser();
          const doc = parser.parseFromString(res.responseText, "text/html");
          const a = doc.querySelector("a");
          if (a) {
            RedirectApp.removeLinkRedirect(this, element, a.href);
          }
        },
      },
      {
        name: "NodeSeek",
        urlTest: /www\.nodeseek\.com/,
        linkTest: /www\.nodeseek\.com\/jump\?to=(.*)/,
        resolveRedirect: function (element) {
          RedirectApp.removeLinkRedirect(
            this,
            element,
            new URL(element.href).searchParams.get("to")
          );
        },
      },
      {
        name: "Google搜索",
        urlTest: /w+\.google\./,
        linkTest: false,
        onInit: function () {
          window.addEventListener("mousedown", function (event) {
            const $a = event?.target?.closest("a");
            if ($a && !$a.getAttribute(RedirectApp.REDIRECT_COMPLETED)) {
              const oldUrl = $a.href;
              const newUrl = decodeURIComponent(
                new URL($a.href).searchParams.get("url")
              );
              $a.href = newUrl == "null" ? oldUrl : newUrl;
              $a.setAttribute(RedirectApp.REDIRECT_COMPLETED, "true");
            }
          });
        },
      },
    ];
  }

  const autoJumpApp = new AutoJumpApp();
  const autoJumpResult = autoJumpApp.bootstrap();
  if (autoJumpResult) return;

  const redirectApp = new RedirectApp();
  redirectApp.bootstrap();
})();