Reader

脱裤子放屁 - 你们讨厌这样的页面吗?

| 掘金本周最热 | Default

前言

平时在逛掘金和少数派等网站的时候,经常有跳转外链的场景,此时基本都会被中转到一个官方提供的提示页面。

掘金:

site-juejin.png

知乎:

site-zhihu.png

少数派:

site-sspai.png

这种官方脱裤子放屁的行为实在令人恼火。是、是、是、我当然知道这么做有很多冠冕堂皇的理由,比如:

  • 防止钓鱼攻击
  • 增强用户意识
  • 品牌保护
  • 遵守法律法规
  • 控制流量去向

(以上5点是 AI 告诉我的理由)

但是作为混迹多年的互联网用户,什么链接可以点,什么最好不要点(悄悄的点) 我还是具备判断能力的。

互联网的本质就是自由穿梭,一个 A 标签就可以让你在整个互联网翱翔,现在你每次起飞的时候都被摁住强迫你阅读一次免责声明,多少是有点恼火的。

解决方案

这些中转站的实现逻辑基本都是将目标地址挂在中转地址的target 参数后面,在中转站做免责声明,然后点击继续跳转才跳到目标网站。

掘金:

https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.apple.com%2Fcn%2Fdesign%2Fhuman-interface-guidelines%2Fapp-icons%23macOS/

少数派: https://sspai.com/link?target=https%3A%2F%2Fgeoguess.games%2F

知乎: https://link.zhihu.com/?target=https%3A//asciidoctor.org/

所以我们就可以写一个浏览器插件,在这些网站中,找出命中外链的 A 标签,替换掉它的 href 属性(只保留 target 后面的真实目标地址)。

核心函数:


function findByTarget() {
  if (!hostnames.includes(location.hostname)) return;
  const linkKeyword = "?target=";
  const aLinks = document.querySelectorAll(
    `a[href*="${linkKeyword}"]:not([data-redirect-skipper])`
  );
  if (!aLinks) return;
  aLinks.forEach((a) => {
    const href = a.href;
    const targetIndex = href.indexOf(linkKeyword);
    if (targetIndex !== -1) {
      const newHref = href.substring(targetIndex + linkKeyword.length);
      a.href = decodeURIComponent(newHref);
      a.setAttribute("data-redirect-skipper", "true");
    }
  });
}

为此我创建了一个项目仓库 redirect-skipper ,并且将该浏览器插件发布在谷歌商店了 安装地址

安装并启用这个浏览器插件之后,在这些网站中点击外链就不会看到中转页面了,而是直接跳转到目标网站。

因为我目前明确需要修改的就是这几个网站,如果大家愿意使用这个插件,且有其他网站需要添加到替换列表的,可以给 redirect-skipper 仓库 提PR。

如果需要添加的网站的转换规则是和 findByTarget 一致的,那么仅需更新 sites.json 文件即可。

如果需要添加的网站的转换规则是独立的,那么需要更新插件代码,合并之后,由我向谷歌商店发起更新。

为了后期可以灵活更新配置(谷歌商店审核太慢了),我默认将插件应用于所有网站,然后在代码里通过 hostname 来判断是否真的需要执行。

{
    "$schema": "https://json.schemastore.org/chrome-manifest.json",
    "name": "redirect-skipper",
    "manifest_version": 3,
    "content_scripts": [
        {
            "matches": ["<all_urls>"],
            "js": ["./scripts/redirect-skipper.js"],
            "run_at": "document_end"
        }
    ],
}

在当前仓库里维护一份 sites.json 的配置表,格式如下:

{
    "description": "远程配置可以开启 Redirect-Skipper 插件的网站 (因为谷歌商店审核太慢了,否则无需通过远程配置,增加复杂性)",
    "sites": [
        {
            "hostname": "juejin.cn",
            "title": "掘金"
        },
        {
            "hostname": "sspai.com",
            "title": "少数派"
        },
        {
            "hostname": "www.zhihu.com",
            "title": "知乎"
        }
    ]
}

这样插件在拉取到这份数据的时候,就可以根据这边描述的网站配置,决定是否执行具体代码。

插件完整代码:

function replaceALinks() {
  findByTarget();
}

function observerDocument() {
  const mb = new MutationObserver((mutationsList) => {
    for (const mutation of mutationsList) {
      if (mutation.type === "childList") {
        if (mutation.addedNodes.length) {
          replaceALinks();
        }
      }
    }
  });
  mb.observe(document, { childList: true, subtree: true });
}

// 监听路由等事件
["hashchange", "popstate", "load"].forEach((event) => {
  window.addEventListener(event, async () => {
    replaceALinks();
    if (event === "load") {
      observerDocument();
      await updateHostnames();
      replaceALinks(); // 更新完数据后再执行一次
    }
  });
});

let hostnames = ["juejin.cn", "sspai.com", "www.zhihu.com"];

function updateHostnames() {
  return fetch(
    "https://raw.githubusercontent.com/dogodo-cc/redirect-skipper/master/sites.json"
  )
    .then((response) => {
      if (response.ok) {
        return response.json();
      }
      throw new Error("Network response was not ok");
    })
    .then((data) => {
      // 如果拉到了远程数据,就用远程的
      hostnames = data.sites.map((site) => {
        return site.hostname;
      });
    })
    .catch((error) => {
      console.error(error);
    });
}

// 符合 '?target=' 格式的链接
// https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.apple.com%2Fcn%2Fdesign%2Fhuman-interface-guidelines%2Fapp-icons%23macOS/
// https://sspai.com/link?target=https%3A%2F%2Fgeoguess.games%2F
// https://link.zhihu.com/?target=https%3A//asciidoctor.org/

function findByTarget() {
  if (!hostnames.includes(location.hostname)) return;
  const linkKeyword = "?target=";
  const aLinks = document.querySelectorAll(
    `a[href*="${linkKeyword}"]:not([data-redirect-skipper])`
  );
  if (!aLinks) return;
  aLinks.forEach((a) => {
    const href = a.href;
    const targetIndex = href.indexOf(linkKeyword);
    if (targetIndex !== -1) {
      const newHref = href.substring(targetIndex + linkKeyword.length);
      a.href = decodeURIComponent(newHref);
      a.setAttribute("data-redirect-skipper", "true");
    }
  });
}

更详细的流程可以查看 redirect-skipper 仓库地址

标题历史

  • 浏览器插件之《跳过第三方链接的提示中转页》