去除链接重定向

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

  1. // ==UserScript==
  2. // @name 去除链接重定向
  3. // @author Meriel
  4. // @description 能原地解析的链接绝不在后台访问,去除重定向的过程快速且高效,平均时间在0.02ms~0.05ms之间。几乎没有任何在后台访问网页获取去重链接的操作,一切都在原地进行,对速度精益求精。去除网页内链接的重定向,具有高准确性和高稳定性,以及相比同类插件更低的时间占用。并且保证去除重定向的有效性,采用三级方案,原地解析->自动跳转->后台访问,保证了一定能去除重定向链接
  5. // @version 2.8.0
  6. // @namespace Violentmonkey Scripts
  7. // @grant GM.xmlHttpRequest
  8. // @match *://*/*
  9. // @connect *
  10. // @icon https://cdn-icons-png.flaticon.com/512/208/208895.png
  11. // @supportURL https://github.com/MerielVaren/remove-link-redirects
  12. // @homepage https://gf.qytechs.cn/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
  13. // @run-at document-end
  14. // @namespace https://gf.qytechs.cn/zh-CN/users/876245-meriel-varen
  15. // @license MIT
  16. // ==/UserScript==
  17.  
  18. (() => {
  19. /********** 以下为自动跳转部分 **********/
  20. class AutoJumpApp {
  21. constructor() {
  22. this.registeredProvider = void 0;
  23. }
  24.  
  25. /**
  26. * 注册(不可用)服务提供者
  27. * @param providers
  28. */
  29. registerProvider() {
  30. for (const provider of AutoJumpApp.providers) {
  31. if (
  32. provider.urlTest instanceof RegExp &&
  33. !provider.urlTest.test(location.href)
  34. ) {
  35. continue;
  36. }
  37. if (provider.urlTest === false) {
  38. continue;
  39. }
  40. if (typeof provider.urlTest === "function" && !provider.urlTest()) {
  41. continue;
  42. }
  43. this.registeredProvider = provider;
  44. break;
  45. }
  46. return this;
  47. }
  48.  
  49. /**
  50. * 启动应用
  51. * @returns
  52. * */
  53. bootstrap() {
  54. this.registerProvider();
  55. if (this.registeredProvider) {
  56. this.registeredProvider.resolveAutoJump();
  57. return true;
  58. }
  59. return false;
  60. }
  61.  
  62. static providers = [
  63. {
  64. name: "酷安",
  65. urlTest: /www\.coolapk\.com\/link\?.*url=(.*)/,
  66. resolveAutoJump: function () {
  67. location.href = decodeURIComponent(
  68. new URL(location.href).searchParams.get("url")
  69. );
  70. },
  71. },
  72. {
  73. name: "CSDN",
  74. urlTest: /link\.csdn\.net\/\?.*target=(.*)/,
  75. resolveAutoJump: function () {
  76. location.href = decodeURIComponent(
  77. new URL(location.href).searchParams.get("target")
  78. );
  79. },
  80. },
  81. {
  82. name: "腾讯兔小巢",
  83. urlTest: /support\.qq\.com\/.*link-jump\?jump=(.*)/,
  84. resolveAutoJump: function () {
  85. location.href = decodeURIComponent(
  86. new URL(location.href).searchParams.get("jump")
  87. );
  88. },
  89. },
  90. {
  91. name: "QQ邮箱",
  92. urlTest: /mail\.qq\.com\/.*gourl=(.*)/,
  93. resolveAutoJump: function () {
  94. location.href = decodeURIComponent(
  95. new URL(location.href).searchParams.get("gourl")
  96. );
  97. },
  98. },
  99. {
  100. name: "印象笔记",
  101. urlTest: /app\.yinxiang\.com\/OutboundRedirect\.action\?.*dest=(.*)/,
  102. resolveAutoJump: function () {
  103. location.href = decodeURIComponent(
  104. new URL(location.href).searchParams.get("dest")
  105. );
  106. },
  107. },
  108. {
  109. name: "Youtube",
  110. urlTest: /www\.youtube\.com\/redirect\?.*q=(.*)/,
  111. resolveAutoJump: function () {
  112. location.href = decodeURIComponent(
  113. new URL(location.href).searchParams.get("q")
  114. );
  115. },
  116. },
  117. {
  118. name: "微信开放社区",
  119. urlTest: /developers\.weixin\.qq\.com\/.*href=(.*)/,
  120. resolveAutoJump: function () {
  121. location.href = decodeURIComponent(
  122. new URL(location.href).searchParams.get("href")
  123. );
  124. },
  125. },
  126. {
  127. name: "pc6下载站",
  128. urlTest: /www\.pc6\.com\/.*gourl=(.*)/,
  129. resolveAutoJump: function () {
  130. location.href = decodeURIComponent(
  131. new URL(location.href).searchParams.get("gourl")
  132. );
  133. },
  134. },
  135. {
  136. name: "51CTO博客",
  137. urlTest: /blog\.51cto\.com\/transfer\?(.*)/,
  138. resolveAutoJump: function () {
  139. location.href = decodeURIComponent(
  140. this.urlTest.exec(location.href)[1]
  141. );
  142. },
  143. },
  144. {
  145. name: "QQ",
  146. urlTest: /c\.pc\.qq\.com.*pfurl=(.*)/,
  147. resolveAutoJump: function () {
  148. location.href = decodeURIComponent(
  149. new URL(location.href).searchParams.get("pfurl")
  150. );
  151. },
  152. },
  153. {
  154. name: "QQ",
  155. urlTest: /c\.pc\.qq\.com.*url=(.*)/,
  156. resolveAutoJump: function () {
  157. location.href = decodeURIComponent(
  158. new URL(location.href).searchParams.get("url")
  159. );
  160. },
  161. },
  162. {
  163. name: "UrlShare",
  164. urlTest: /.+\.urlshare\..+\/.*url=(.*)/,
  165. resolveAutoJump: function () {
  166. location.href = decodeURIComponent(
  167. new URL(location.href).searchParams.get("url")
  168. );
  169. },
  170. },
  171. {
  172. name: "腾讯文档",
  173. urlTest: /docs\.qq\.com\/.*\?url=(.*)/,
  174. resolveAutoJump: function () {
  175. location.href = decodeURIComponent(
  176. new URL(location.href).searchParams.get("url")
  177. );
  178. },
  179. },
  180. {
  181. name: "金山文档",
  182. urlTest: /www\.kdocs\.cn\/office\/link\?.*target=(.*)/,
  183. resolveAutoJump: function () {
  184. location.href = decodeURIComponent(
  185. new URL(location.href).searchParams.get("target")
  186. );
  187. },
  188. },
  189. {
  190. name: "NodeSeek",
  191. urlTest: /www\.nodeseek\.com\/jump\?.*to=(.*)/,
  192. resolveAutoJump: function () {
  193. location.href = decodeURIComponent(
  194. new URL(location.href).searchParams.get("to")
  195. );
  196. },
  197. },
  198. {
  199. name: "新版QQ邮箱",
  200. urlTest: /wx\.mail\.qq\.com\/xmspamcheck\/xmsafejump\?/,
  201. resolveAutoJump: function () {
  202. location.href = decodeURIComponent(
  203. new URL(location.href).searchParams.get("url")
  204. );
  205. },
  206. },
  207. ];
  208. }
  209.  
  210. /********** 以下为重定向解析部分 **********/
  211. class RedirectApp {
  212. /**
  213. * 调节providers的顺序
  214. * 将匹配到的provider放到最前
  215. * @param provider
  216. */
  217. adjustProviderOrderOnce = (function () {
  218. let executed = false; // 标志变量,用于跟踪函数是否已执行
  219. return function (provider) {
  220. if (!executed) {
  221. const index = this.registeredProviders.indexOf(provider);
  222. if (index !== -1) {
  223. this.registeredProviders.splice(index, 1);
  224. this.registeredProviders.unshift(provider);
  225. }
  226. executed = true;
  227. }
  228. };
  229. })();
  230.  
  231. /**
  232. * A 标签是否匹配服务提供者
  233. * @param element
  234. * @param provider
  235. */
  236. static isMatchProvider(element, provider) {
  237. if (element.getAttribute(RedirectApp.REDIRECT_COMPLETED)) {
  238. return false;
  239. }
  240. if (
  241. provider.linkTest instanceof RegExp &&
  242. !provider.linkTest.test(element.href)
  243. ) {
  244. return false;
  245. }
  246. if (provider.linkTest instanceof Boolean) {
  247. return provider.linkTest;
  248. }
  249. if (
  250. typeof provider.linkTest === "function" &&
  251. !provider.linkTest(element)
  252. ) {
  253. return false;
  254. }
  255. return true;
  256. }
  257.  
  258. /**
  259. * 解析完成的标志
  260. */
  261. static REDIRECT_COMPLETED = "redirect-completed";
  262.  
  263. /**
  264. * 兜底解析器
  265. * 用于解析无法解析的链接
  266. * 通过GM.xmlHttpRequest获取最终链接
  267. */
  268. static FallbackResolver = class {
  269. constructor() {
  270. this.processedUrls = new Map();
  271. }
  272.  
  273. async resolveRedirect(element) {
  274. const href = element.href;
  275.  
  276. if (!this.processedUrls.has(href)) {
  277. // 创建一个新的 Promise 并存储在 Map 中
  278. let resolvePromise;
  279. const promise = new Promise((resolve) => {
  280. resolvePromise = resolve;
  281. });
  282. this.processedUrls.set(href, promise);
  283.  
  284. try {
  285. const res = await GM.xmlHttpRequest({
  286. method: "GET",
  287. url: href,
  288. anonymous: true,
  289. });
  290. if (res.finalUrl) {
  291. const url = res.finalUrl;
  292. this.processedUrls.set(href, url);
  293. element.href = url;
  294. } else {
  295. this.processedUrls.delete(href); // 请求失败时删除占位符
  296. }
  297. } catch (error) {
  298. console.error("请求失败:", error);
  299. this.processedUrls.delete(href); // 请求失败时删除占位符
  300. } finally {
  301. resolvePromise(); // 请求完成后解析 Promise
  302. }
  303. } else {
  304. const cachedValue = this.processedUrls.get(href);
  305.  
  306. if (cachedValue instanceof Promise) {
  307. // 如果是 Promise,等待其完成
  308. await cachedValue;
  309. element.href = this.processedUrls.get(href);
  310. } else {
  311. // 否则直接使用缓存值
  312. element.href = cachedValue;
  313. }
  314. }
  315. }
  316. };
  317.  
  318. /**
  319. * 移除链接重定向
  320. * 首先判断是否可以直接解析链接,如果可以则直接解析
  321. * 如果不行,则调用fallbackResolver解析
  322. * @param caller 调用者
  323. * @param element 链接元素
  324. * @param realUrl 真实链接
  325. * @param options 配置项
  326. * @returns
  327. * */
  328. static removeLinkRedirect(caller, element, realUrl, options) {
  329. element.setAttribute(RedirectApp.REDIRECT_COMPLETED, "true");
  330. if ((realUrl && element.href !== realUrl) || options?.force) {
  331. try {
  332. element.href = decodeURIComponent(realUrl);
  333. } catch (_) {
  334. element.href = realUrl;
  335. }
  336. } else if (caller) {
  337. if (!caller.fallbackResolver) {
  338. caller.fallbackResolver = new RedirectApp.FallbackResolver();
  339. }
  340. caller.fallbackResolver.resolveRedirect(element);
  341. }
  342. }
  343.  
  344. /**
  345. * 监听URL变化
  346. * @param operation
  347. * @returns
  348. * */
  349. static monitorUrlChange(operation) {
  350. function urlChange(event) {
  351. const destinationUrl = event?.destination?.url || "";
  352. if (destinationUrl.startsWith("about:blank")) return;
  353. const href = destinationUrl || location.href;
  354. if (href !== location.href) {
  355. operation(href);
  356. }
  357. }
  358. unsafeWindow?.navigation?.addEventListener("navigate", urlChange);
  359. unsafeWindow.addEventListener("replaceState", urlChange);
  360. unsafeWindow.addEventListener("pushState", urlChange);
  361. unsafeWindow.addEventListener("popState", urlChange);
  362. unsafeWindow.addEventListener("hashchange", urlChange);
  363. }
  364.  
  365. constructor() {
  366. this.registeredProviders = [];
  367. this.mutationObserver = new MutationObserver((mutations) => {
  368. mutations.forEach(this.handleMutation.bind(this));
  369. });
  370. }
  371.  
  372. /**
  373. * 处理变动
  374. * @param mutation
  375. * @returns
  376. * */
  377. handleMutation(mutation) {
  378. if (mutation.type === "childList") {
  379. mutation.addedNodes.forEach((node) => {
  380. if (node instanceof HTMLAnchorElement) {
  381. this.handleNode(node);
  382. } else {
  383. // 有些网站被observer观察到的是一个div,里面包含了很多a标签
  384. // 这种情况下,需要对所有的a标签进行处理
  385. node
  386. ?.querySelectorAll?.(`a:not([${RedirectApp.REDIRECT_COMPLETED}])`)
  387. ?.forEach((aNode) => this.handleNode(aNode));
  388. }
  389. });
  390. }
  391. }
  392.  
  393. /**
  394. * 处理节点
  395. * @param node
  396. * @returns
  397. */
  398. handleNode(node) {
  399. for (const provider of this.registeredProviders) {
  400. if (RedirectApp.isMatchProvider(node, provider)) {
  401. provider.resolveRedirect(node);
  402. this.adjustProviderOrderOnce(provider);
  403. break;
  404. }
  405. }
  406. }
  407.  
  408. /**
  409. * 当页面准备就绪时,进行初始化动作
  410. * 有一些服务提供者需要在页面准备就绪时进行特殊的初始化操作
  411. * 比如百度搜索,需要监听URL变化
  412. * 以及一些情况不需要RediectApp介入
  413. * 如谷歌搜索需要监听的是href变化,而链接本身没有重定向
  414. */
  415. async initProviders() {
  416. for (const provider of this.registeredProviders) {
  417. if (provider.onInit) {
  418. await provider.onInit();
  419. }
  420. }
  421. }
  422.  
  423. /**
  424. * 注册(不可用)服务提供者
  425. * @param providers
  426. */
  427. registerProviders() {
  428. for (const provider of RedirectApp.providers) {
  429. if (provider.urlTest === false) {
  430. continue;
  431. }
  432. if (
  433. provider.urlTest instanceof RegExp &&
  434. !provider.urlTest.test(location.href)
  435. ) {
  436. continue;
  437. }
  438. if (typeof provider.urlTest === "function" && !provider.urlTest()) {
  439. continue;
  440. }
  441. this.registeredProviders.push(provider);
  442. }
  443. return this;
  444. }
  445.  
  446. /**
  447. * 启动应用
  448. */
  449. bootstrap() {
  450. this.registerProviders();
  451. document.querySelectorAll("a").forEach((element) => {
  452. this.handleNode(element);
  453. });
  454. console.log("去除重定向服务正在运行:", this.registeredProviders);
  455. addEventListener("DOMContentLoaded", this.initProviders.bind(this));
  456. this.mutationObserver.observe(document, {
  457. childList: true,
  458. subtree: true,
  459. });
  460. }
  461.  
  462. static providers = [
  463. {
  464. name: "如有乐享",
  465. urlTest: /51\.ruyo\.net/,
  466. linkTest: /\/[^\?]*\?u=(.*)/,
  467. resolveRedirect: function (element) {
  468. RedirectApp.removeLinkRedirect(
  469. this,
  470. element,
  471. new URL(element.href).searchParams.get("u")
  472. );
  473. },
  474. },
  475. {
  476. name: "Mozilla",
  477. urlTest: /addons\.mozilla\.org/,
  478. linkTest: /outgoing\.prod\.mozaws\.net\/v\d\/\w+\/(.*)/,
  479. resolveRedirect: function (element) {
  480. let url = void 0;
  481. const match = this.linkTest.exec(element.href);
  482. if (match && match[1]) {
  483. try {
  484. url = decodeURIComponent(match[1]);
  485. } catch (_) {
  486. url = /(http|https)?:\/\//.test(match[1]) ? match[1] : void 0;
  487. }
  488. }
  489. RedirectApp.removeLinkRedirect(this, element, url);
  490. },
  491. },
  492. {
  493. name: "爱发电",
  494. urlTest: /afdian\.net/,
  495. linkTest: /afdian\.net\/link\?target=(.*)/,
  496. resolveRedirect: function (element) {
  497. RedirectApp.removeLinkRedirect(
  498. this,
  499. element,
  500. new URL(element.href).searchParams.get("target")
  501. );
  502. },
  503. },
  504. {
  505. name: "印象笔记",
  506. urlTest: /(www|app)\.yinxiang\.com/,
  507. linkTest: true,
  508. resolveRedirect: function (element) {
  509. if (element.hasAttribute("data-mce-href")) {
  510. if (!element.onclick) {
  511. RedirectApp.removeLinkRedirect(this, element, element.href, {
  512. force: true,
  513. });
  514. element.onclick = function (e) {
  515. // 阻止事件冒泡, 因为上层元素绑定的click事件会重定向
  516. e.stopPropagation?.();
  517. element.setAttribute("target", "_blank");
  518. window.top
  519. ? window.top.open(element.href)
  520. : window.open(element.href);
  521. };
  522. }
  523. }
  524. },
  525. onInit: async function () {
  526. const handler = function (e) {
  527. const dom = e.target;
  528. const tagName = dom.tagName.toUpperCase();
  529. switch (tagName) {
  530. case "A": {
  531. this.resolveRedirect(dom);
  532. break;
  533. }
  534. case "IFRAME": {
  535. if (dom.hasAttribute("redirect-link-removed")) {
  536. return;
  537. }
  538. dom.setAttribute("redirect-link-removed", "true");
  539. dom.contentWindow.document.addEventListener(
  540. "mouseover",
  541. handler
  542. );
  543. break;
  544. }
  545. }
  546. };
  547. document.addEventListener("mouseover", handler);
  548. },
  549. },
  550. {
  551. name: "印象笔记",
  552. urlTest: /app\.yinxiang\.com/,
  553. linkTest:
  554. /(www|app)\.yinxiang\.com\/OutboundRedirect\.action\?dest=(.*)/,
  555. resolveRedirect: function (element) {
  556. RedirectApp.removeLinkRedirect(
  557. this,
  558. element,
  559. new URL(element.href).searchParams.get("dest")
  560. );
  561. },
  562. },
  563. {
  564. name: "Bing",
  565. urlTest: /bing\.com/,
  566. linkTest: /.+\.bing\.com\/ck\/a\?.*&u=a1(.*)&ntb=1/,
  567. textDecoder: new TextDecoder("utf-8"),
  568. resolveRedirect: function (element) {
  569. RedirectApp.removeLinkRedirect(
  570. this,
  571. element,
  572. this.textDecoder.decode(
  573. Uint8Array.from(
  574. Array.from(
  575. atob(
  576. element.href
  577. .split("&u=a1")[1]
  578. .split("&ntb=1")[0]
  579. .replace(/[-_]/g, (e) => ("-" === e ? "+" : "/"))
  580. .replace(/[^A-Za-z0-9\\+\\/]/g, "")
  581. )
  582. ).map((e) => e.charCodeAt(0))
  583. )
  584. )
  585. );
  586. },
  587. },
  588. {
  589. name: "51CTO博客",
  590. urlTest: /blog\.51cto\.com/,
  591. linkTest: true,
  592. resolveRedirect: function (element) {
  593. const container = document.querySelector(".article-detail");
  594. if (container?.contains(element)) {
  595. if (!element.onclick && element.href) {
  596. element.onclick = function (e) {
  597. e.stopPropagation?.();
  598. const $a = document.createElement("a");
  599. $a.href = element.href;
  600. $a.target = element.target;
  601. $a.click();
  602. };
  603. }
  604. }
  605. },
  606. },
  607. {
  608. name: "51CTO博客",
  609. urlTest: /blog\.51cto\.com/,
  610. linkTest: /blog\.51cto\.com\/.*transfer\?(.*)/,
  611. resolveRedirect: function (element) {
  612. RedirectApp.removeLinkRedirect(
  613. this,
  614. element,
  615. new URL(element.href).searchParams.get("url")
  616. );
  617. },
  618. },
  619. {
  620. name: "CSDN",
  621. urlTest: /blog\.csdn\.net/,
  622. linkTest: true,
  623. resolveRedirect: function (element) {
  624. const container = document.querySelector("#content_views");
  625. if (!container?.contains(element)) {
  626. return;
  627. }
  628.  
  629. if (!element.onclick && element.origin !== window.location.origin) {
  630. RedirectApp.removeLinkRedirect(this, element, element.href, {
  631. force: true,
  632. });
  633. element.onclick = function (e) {
  634. // 阻止事件冒泡, 因为上层元素绑定的click事件会重定向
  635. e.stopPropagation?.();
  636. e.preventDefault?.();
  637. e.stopImmediatePropagation?.();
  638. };
  639. }
  640. },
  641. },
  642. {
  643. name: "知乎日报",
  644. urlTest: /daily\.zhihu\.com/,
  645. linkTest: /zhihu\.com\/\?target=(.*)/,
  646. resolveRedirect: function (element) {
  647. RedirectApp.removeLinkRedirect(
  648. this,
  649. element,
  650. new URL(element.href).searchParams.get("target")
  651. );
  652. },
  653. },
  654. {
  655. name: "Google Docs",
  656. urlTest: /docs\.google\.com/,
  657. linkTest: /www\.google\.com\/url\?q=(.*)/,
  658. resolveRedirect: function (element) {
  659. RedirectApp.removeLinkRedirect(
  660. this,
  661. element,
  662. new URL(element.href).searchParams.get("q")
  663. );
  664. },
  665. },
  666. {
  667. name: "Pocket",
  668. urlTest: /getpocket\.com/,
  669. linkTest: /getpocket\.com\/redirect\?url=(.*)/,
  670. resolveRedirect: function (element) {
  671. RedirectApp.removeLinkRedirect(
  672. this,
  673. element,
  674. new URL(element.href).searchParams.get("url")
  675. );
  676. },
  677. },
  678. {
  679. name: "Gitee",
  680. urlTest: /gitee\.com/,
  681. linkTest: /gitee\.com\/link\?target=(.*)/,
  682. resolveRedirect: function (element) {
  683. RedirectApp.removeLinkRedirect(
  684. this,
  685. element,
  686. new URL(element.href).searchParams.get("target")
  687. );
  688. },
  689. },
  690. {
  691. name: "InfoQ",
  692. urlTest: /infoq\.cn/,
  693. linkTest: /infoq\.cn\/link\?target=(.*)/,
  694. resolveRedirect: function (element) {
  695. RedirectApp.removeLinkRedirect(
  696. this,
  697. element,
  698. new URL(element.href).searchParams.get("target")
  699. );
  700. },
  701. },
  702. {
  703. name: "掘金",
  704. urlTest: /juejin\.(im|cn)/,
  705. linkTest: /link\.juejin\.(im|cn)\/\?target=(.*)/,
  706. resolveRedirect: function (element) {
  707. const finalURL = new URL(element.href).searchParams.get("target");
  708. RedirectApp.removeLinkRedirect(this, element, finalURL);
  709. if (this.linkTest.test(element.title)) {
  710. element.title = finalURL;
  711. }
  712. },
  713. },
  714. {
  715. name: "QQ邮箱",
  716. urlTest: /mail\.qq\.com/,
  717. linkTest: true,
  718. resolveRedirect: function (element) {
  719. const container = document.querySelector("#contentDiv");
  720. if (container?.contains(element)) {
  721. if (!element.onclick) {
  722. element.onclick = function (e) {
  723. // 阻止事件冒泡, 因为上层元素绑定的click事件会重定向
  724. e.stopPropagation?.();
  725. };
  726. }
  727. }
  728. },
  729. },
  730. {
  731. name: "QQ邮箱",
  732. urlTest: /mail\.qq\.com/,
  733. linkTest: /mail\.qq\.com.+gourl=(.+).*/,
  734. resolveRedirect: function (element) {
  735. RedirectApp.removeLinkRedirect(
  736. this,
  737. element,
  738. new URL(element.href).searchParams.get("gourl")
  739. );
  740. },
  741. },
  742. {
  743. name: "新版QQ邮箱",
  744. urlTest: () => {
  745. if (
  746. /mail\.qq\.com/.test(location.href) ||
  747. /wx\.mail\.qq\.com/.test(location.href)
  748. ) {
  749. return true;
  750. }
  751. return false;
  752. },
  753. linkTest: /wx\.mail\.qq\.com\/xmspamcheck\/xmsafejump\?/,
  754. resolveRedirect: function (element) {
  755. const url = new URL(element.href).searchParams.get("url");
  756. RedirectApp.removeLinkRedirect(this, element, url);
  757. },
  758. },
  759. {
  760. name: "OS China",
  761. urlTest: /oschina\.net/,
  762. linkTest: /oschina\.net\/action\/GoToLink\?url=(.*)/,
  763. resolveRedirect: function (element) {
  764. RedirectApp.removeLinkRedirect(
  765. this,
  766. element,
  767. new URL(element.href).searchParams.get("url")
  768. );
  769. },
  770. },
  771. {
  772. name: "Google Play",
  773. urlTest: /play\.google\.com/,
  774. linkTest: function (element) {
  775. if (/google\.com\/url\?q=(.*)/.test(element.href)) {
  776. return true;
  777. } else if (/^\/store\/apps\/details/.test(location.pathname)) {
  778. return true;
  779. }
  780. return false;
  781. },
  782. resolveRedirect: function (element) {
  783. RedirectApp.removeLinkRedirect(
  784. this,
  785. element,
  786. new URL(element.href).searchParams.get("q")
  787. );
  788. const eles = [].slice.call(document.querySelectorAll("a.hrTbp"));
  789. for (const ele of eles) {
  790. if (!ele.href || ele.getAttribute(RedirectApp.REDIRECT_COMPLETED)) {
  791. continue;
  792. }
  793. ele.setAttribute(RedirectApp.REDIRECT_COMPLETED, "true");
  794. ele.setAttribute("target", "_blank");
  795. ele.addEventListener(
  796. "click",
  797. (event) => {
  798. event.stopPropagation();
  799. },
  800. true
  801. );
  802. }
  803. },
  804. },
  805. {
  806. name: "少数派",
  807. urlTest: /sspai\.com/,
  808. linkTest: /sspai\.com\/link\?target=(.*)/,
  809. resolveRedirect: function (element) {
  810. RedirectApp.removeLinkRedirect(
  811. this,
  812. element,
  813. new URL(element.href).searchParams.get("target")
  814. );
  815. },
  816. },
  817. {
  818. name: "Steam Community",
  819. urlTest: /steamcommunity\.com/,
  820. linkTest: /steamcommunity\.com\/linkfilter\/\?url=(.*)/,
  821. resolveRedirect: function (element) {
  822. RedirectApp.removeLinkRedirect(
  823. this,
  824. element,
  825. new URL(element.href).searchParams.get("url")
  826. );
  827. },
  828. },
  829. {
  830. name: "百度贴吧",
  831. urlTest: /tieba\.baidu\.com/,
  832. linkTest: /jump\d*\.bdimg\.com/,
  833. resolveRedirect: function (element) {
  834. let url = void 0;
  835. const text = element.innerText || element.textContent || void 0;
  836. const isUrl = /(http|https)?:\/\//.test(text);
  837. if (isUrl) {
  838. try {
  839. url = decodeURIComponent(text);
  840. } catch (_) {
  841. url = text;
  842. }
  843. }
  844. RedirectApp.removeLinkRedirect(this, element, url);
  845. },
  846. },
  847. {
  848. name: "Twitter",
  849. urlTest: /(twitter|x)\.com/,
  850. linkTest: /t\.co\/\w+/,
  851. resolveRedirect: async function (element) {
  852. if (/(http|https)?:\/\//.test(element.title)) {
  853. const url = decodeURIComponent(element.title);
  854. RedirectApp.removeLinkRedirect(this, element, url);
  855. return;
  856. }
  857. const textContent = element.textContent.replace(/…$/, "");
  858. if (/(http|https)?:\/\//.test(textContent)) {
  859. RedirectApp.removeLinkRedirect(this, element, textContent);
  860. return;
  861. } else {
  862. const res = await GM.xmlHttpRequest({
  863. method: "GET",
  864. url: "https://" + textContent,
  865. anonymous: true,
  866. });
  867. if (res.status === 200) {
  868. RedirectApp.removeLinkRedirect(this, element, res.finalUrl);
  869. } else {
  870. RedirectApp.removeLinkRedirect(
  871. this,
  872. element,
  873. "http://" + textContent
  874. );
  875. }
  876. }
  877. },
  878. },
  879. {
  880. name: "微博",
  881. urlTest: /\.weibo\.(com|cn)/,
  882. linkTest: /t\.cn\/\w+/,
  883. resolveRedirect: function (element) {
  884. if (!/^(http|https)?:\/\//.test(element.title)) {
  885. return;
  886. }
  887. let url = void 0;
  888. try {
  889. url = decodeURIComponent(element.title);
  890. } catch (_) {}
  891. RedirectApp.removeLinkRedirect(this, element, url);
  892. },
  893. },
  894. {
  895. name: "微博",
  896. urlTest: /weibo\.(com|cn)/,
  897. linkTest: /weibo\.(com|cn)\/sinaurl\?u=(.*)/,
  898. resolveRedirect: function (element) {
  899. RedirectApp.removeLinkRedirect(
  900. this,
  901. element,
  902. decodeURIComponent(new URL(element.href).searchParams.get("u"))
  903. );
  904. },
  905. },
  906. {
  907. name: "百度搜索",
  908. urlTest: /www\.baidu\.com/,
  909. linkTest: /www\.baidu\.com\/link\?url=/,
  910. unresolvableWebsites: ["nourl.ubs.baidu.com", "lightapp.baidu.com"],
  911. specialElements: [
  912. ".cos-row",
  913. ".c-group-wrapper",
  914. ".subLink_answer",
  915. ".subLink_answer ~ a",
  916. "[class*=catalog-list]",
  917. "[class*=group-content]",
  918. ],
  919. fallbackResolver: new RedirectApp.FallbackResolver(),
  920. resolveRedirect: async function (element) {
  921. const url = this.specialElements.some((selector) =>
  922. element.closest(selector)
  923. )
  924. ? void 0
  925. : element.closest(".c-container[mu]")?.getAttribute("mu");
  926. if (
  927. url &&
  928. url !== "null" &&
  929. url !== "undefined" &&
  930. url !== "" &&
  931. url !== "about:blank" &&
  932. url !== "javascript:void(0);" &&
  933. !this.unresolvableWebsites.some((u) => url?.includes(u))
  934. ) {
  935. RedirectApp.removeLinkRedirect(this, element, url);
  936. } else {
  937. this.fallbackResolver.resolveRedirect(element);
  938. }
  939. },
  940. // onInit: async function () {
  941. // RedirectApp.monitorUrlChange((href) => {
  942. // const url = new URL(location.href);
  943. // if (url.searchParams.has("wd")) {
  944. // location.href = href;
  945. // }
  946. // });
  947. // },
  948. },
  949. {
  950. name: "豆瓣",
  951. urlTest: /douban\.com/,
  952. linkTest: /douban\.com\/link2\/?\?url=(.*)/,
  953. resolveRedirect: function (element) {
  954. RedirectApp.removeLinkRedirect(
  955. this,
  956. element,
  957. new URL(element.href).searchParams.get("url")
  958. );
  959. },
  960. },
  961. {
  962. name: "Google搜索",
  963. urlTest: /\w+\.google\./,
  964. linkTest: true,
  965. resolveRedirect: function (element) {
  966. const traceProperties = [
  967. "ping",
  968. "data-jsarwt",
  969. "data-usg",
  970. "data-ved",
  971. ];
  972. // 移除追踪
  973. for (const property of traceProperties) {
  974. if (element.getAttribute(property)) {
  975. element.removeAttribute(property);
  976. }
  977. }
  978. // 移除多余的事件
  979. if (element.getAttribute("onmousedown")) {
  980. element.removeAttribute("onmousedown");
  981. }
  982. // 尝试去除重定向
  983. if (element.getAttribute("data-href")) {
  984. const realUrl = element.getAttribute("data-href");
  985. RedirectApp.removeLinkRedirect(this, element, realUrl);
  986. }
  987. if (element && element.href) {
  988. const url = new URL(element?.href);
  989. if (url?.searchParams.get("url")) {
  990. RedirectApp.removeLinkRedirect(
  991. this,
  992. element,
  993. url.searchParams.get("url")
  994. );
  995. }
  996. }
  997. },
  998. },
  999. {
  1000. name: "简书",
  1001. urlTest: /www\.jianshu\.com/,
  1002. linkTest: function (element) {
  1003. const isLink1 = /links\.jianshu\.com\/go/.test(element.href);
  1004. const isLink2 = /link\.jianshu\.com(\/)?\?t=/.test(element.href);
  1005. const isLink3 = /jianshu\.com\/go-wild\/?\?(.*)url=/.test(
  1006. element.href
  1007. );
  1008. if (isLink1 || isLink2 || isLink3) {
  1009. return true;
  1010. }
  1011. return false;
  1012. },
  1013. resolveRedirect: function (element) {
  1014. const search = new URL(element.href).searchParams;
  1015. RedirectApp.removeLinkRedirect(
  1016. this,
  1017. element,
  1018. search.get("to") || search.get("t") || search.get("url")
  1019. );
  1020. },
  1021. onInit: async function () {
  1022. document
  1023. .querySelectorAll(`a:not[${RedirectApp.REDIRECT_COMPLETED}]`)
  1024. .forEach((element) => {
  1025. if (this.linkTest(element)) {
  1026. this.resolveRedirect(element);
  1027. }
  1028. });
  1029. },
  1030. },
  1031. {
  1032. name: "标志情报局",
  1033. urlTest: /www\.logonews\.cn/,
  1034. linkTest: /link\.logonews\.cn\/\?url=(.*)/,
  1035. resolveRedirect: function (element) {
  1036. RedirectApp.removeLinkRedirect(
  1037. this,
  1038. element,
  1039. new URL(element.href).searchParams.get("url")
  1040. );
  1041. },
  1042. },
  1043. {
  1044. name: "360搜索",
  1045. urlTest: /www\.so\.com/,
  1046. linkTest: /so\.com\/link\?(.*)/,
  1047. resolveRedirect: function (element) {
  1048. const url =
  1049. element.getAttribute("data-mdurl") ||
  1050. element.getAttribute("e-landurl");
  1051. if (url) {
  1052. RedirectApp.removeLinkRedirect(this, element, url);
  1053. }
  1054. // remove track
  1055. element.removeAttribute("e_href");
  1056. element.removeAttribute("data-res");
  1057. },
  1058. },
  1059. {
  1060. name: "搜狗搜索",
  1061. urlTest: /www\.sogou\.com/,
  1062. linkTest: /www\.sogou\.com\/link\?url=/,
  1063. resolveRedirect: function (element) {
  1064. const vrwrap = element.closest(".vrwrap");
  1065. const rSech = vrwrap.querySelector(".r-sech[data-url]");
  1066. const url = rSech.getAttribute("data-url");
  1067. RedirectApp.removeLinkRedirect(this, element, url);
  1068. },
  1069. },
  1070. {
  1071. name: "Youtube",
  1072. urlTest: /www\.youtube\.com/,
  1073. linkTest: /www\.youtube\.com\/redirect\?.{1,}/,
  1074. resolveRedirect: function (element) {
  1075. RedirectApp.removeLinkRedirect(
  1076. this,
  1077. element,
  1078. new URL(element.href).searchParams.get("q")
  1079. );
  1080. },
  1081. },
  1082. {
  1083. name: "知乎",
  1084. urlTest: /www\.zhihu\.com/,
  1085. linkTest: /zhihu\.com\/\?target=(.*)/,
  1086. resolveRedirect: function (element) {
  1087. RedirectApp.removeLinkRedirect(
  1088. this,
  1089. element,
  1090. new URL(element.href).searchParams.get("target")
  1091. );
  1092. },
  1093. },
  1094. {
  1095. name: "百度学术",
  1096. urlTest: /xueshu\.baidu\.com/,
  1097. linkTest: /xueshu\.baidu\.com\/s?\?(.*)/,
  1098. resolveRedirect: function (element) {
  1099. const url =
  1100. element.getAttribute("data-link") ||
  1101. element.getAttribute("data-url") ||
  1102. void 0;
  1103. RedirectApp.removeLinkRedirect(
  1104. this,
  1105. element,
  1106. decodeURIComponent(url)
  1107. );
  1108. },
  1109. },
  1110. {
  1111. name: "知乎专栏",
  1112. urlTest: /zhuanlan\.zhihu\.com/,
  1113. linkTest: /link\.zhihu\.com\/\?target=(.*)/,
  1114. resolveRedirect: function (element) {
  1115. RedirectApp.removeLinkRedirect(
  1116. this,
  1117. element,
  1118. new URL(element.href).searchParams.get("target")
  1119. );
  1120. },
  1121. },
  1122. {
  1123. name: "力扣",
  1124. urlTest: /leetcode\.(cn|com)/,
  1125. linkTest: /leetcode\.(cn|com)\/link\?target=(.*)/,
  1126. resolveRedirect: function (element) {
  1127. RedirectApp.removeLinkRedirect(
  1128. this,
  1129. element,
  1130. new URL(element.href).searchParams.get("target")
  1131. );
  1132. },
  1133. },
  1134. {
  1135. name: "腾讯开发者社区",
  1136. urlTest: /cloud\.tencent\.com/,
  1137. linkTest:
  1138. /cloud\.tencent\.com\/developer\/tools\/blog-entry\?target=(.*)/,
  1139. resolveRedirect: function (element) {
  1140. RedirectApp.removeLinkRedirect(
  1141. this,
  1142. element,
  1143. new URL(element.href).searchParams.get("target")
  1144. );
  1145. },
  1146. },
  1147. {
  1148. name: "酷安",
  1149. urlTest: true,
  1150. linkTest: /www\.coolapk\.com\/link\?url=(.*)/,
  1151. resolveRedirect: function (element) {
  1152. RedirectApp.removeLinkRedirect(
  1153. this,
  1154. element,
  1155. new URL(element.href).searchParams.get("url")
  1156. );
  1157. },
  1158. },
  1159. {
  1160. name: "腾讯兔小巢",
  1161. urlTest: /support\.qq\.com/,
  1162. linkTest: /support\.qq\.com\/.*link-jump\?jump=(.*)/,
  1163. resolveRedirect: function (element) {
  1164. RedirectApp.removeLinkRedirect(
  1165. this,
  1166. element,
  1167. new URL(element.href).searchParams.get("jump")
  1168. );
  1169. },
  1170. },
  1171. {
  1172. name: "微信开放社区",
  1173. urlTest: /developers\.weixin\.qq\.com/,
  1174. linkTest: /developers\.weixin\.qq\.com\/.*href=(.*)/,
  1175. resolveRedirect: function (element) {
  1176. RedirectApp.removeLinkRedirect(
  1177. this,
  1178. element,
  1179. new URL(element.href).searchParams.get("href")
  1180. );
  1181. },
  1182. },
  1183. {
  1184. name: "pc6下载站",
  1185. urlTest: /www\.pc6\.com/,
  1186. linkTest: /www\.pc6\.com\/.*\?gourl=(.*)/,
  1187. customDecode: function (encoded) {
  1188. const key = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  1189. const len = key.length;
  1190. let d = 0;
  1191. let s = new Array(Math.floor(encoded.length / 3));
  1192. const b = s.length;
  1193. for (let i = 0; i < b; i++) {
  1194. const b1 = key.indexOf(encoded.charAt(d++));
  1195. const b2 = key.indexOf(encoded.charAt(d++));
  1196. const b3 = key.indexOf(encoded.charAt(d++));
  1197. s[i] = b1 * len * len + b2 * len + b3;
  1198. }
  1199. const decoded = String.fromCharCode(...s);
  1200. return decoded;
  1201. },
  1202. resolveRedirect: function (element) {
  1203. RedirectApp.removeLinkRedirect(
  1204. this,
  1205. element,
  1206. this.customDecode(new URL(element.href).searchParams.get("gourl"))
  1207. );
  1208. },
  1209. },
  1210. {
  1211. name: "QQ",
  1212. urlTest: true,
  1213. linkTest: /c\.pc\.qq\.com.*pfurl=(.*)/,
  1214. resolveRedirect: function (element) {
  1215. RedirectApp.removeLinkRedirect(
  1216. this,
  1217. element,
  1218. new URL(element.href).searchParams.get("pfurl")
  1219. );
  1220. },
  1221. },
  1222. {
  1223. name: "QQ",
  1224. urlTest: true,
  1225. linkTest: /c\.pc\.qq\.com.*url=(.*)/,
  1226. resolveRedirect: function (element) {
  1227. RedirectApp.removeLinkRedirect(
  1228. this,
  1229. element,
  1230. decodeURIComponent(new URL(element.href).searchParams.get("url"))
  1231. );
  1232. },
  1233. },
  1234. {
  1235. name: "UrlShare",
  1236. urlTest: function () {
  1237. return ![/www\.jun\.la/].some((r) => r.test(location.href));
  1238. },
  1239. linkTest: /.+\.urlshare\..+\/.*url=(.*)/,
  1240. resolveRedirect: function (element) {
  1241. RedirectApp.removeLinkRedirect(
  1242. this,
  1243. element,
  1244. decodeURIComponent(new URL(element.href).searchParams.get("url"))
  1245. );
  1246. },
  1247. },
  1248. {
  1249. name: "PHP中文网",
  1250. urlTest: /www\.php\.cn/,
  1251. linkTest: /www\.php\.cn\/link(.*)/,
  1252. resolveRedirect: async function (element) {
  1253. const res = await GM.xmlHttpRequest({
  1254. method: "GET",
  1255. url: element.href,
  1256. anonymous: true,
  1257. });
  1258. const parser = new DOMParser();
  1259. const doc = parser.parseFromString(res.responseText, "text/html");
  1260. const a = doc.querySelector("a");
  1261. if (a) {
  1262. RedirectApp.removeLinkRedirect(this, element, a.href);
  1263. }
  1264. },
  1265. },
  1266. {
  1267. name: "NodeSeek",
  1268. urlTest: /www\.nodeseek\.com/,
  1269. linkTest: /www\.nodeseek\.com\/jump\?to=(.*)/,
  1270. resolveRedirect: function (element) {
  1271. RedirectApp.removeLinkRedirect(
  1272. this,
  1273. element,
  1274. new URL(element.href).searchParams.get("to")
  1275. );
  1276. },
  1277. },
  1278. {
  1279. name: "Google搜索",
  1280. urlTest: /w+\.google\./,
  1281. linkTest: false,
  1282. onInit: function () {
  1283. window.addEventListener("mousedown", function (event) {
  1284. const $a = event?.target?.closest("a");
  1285. if ($a && !$a.getAttribute(RedirectApp.REDIRECT_COMPLETED)) {
  1286. const oldUrl = $a.href;
  1287. const newUrl = decodeURIComponent(
  1288. new URL($a.href).searchParams.get("url")
  1289. );
  1290. $a.href = newUrl == "null" ? oldUrl : newUrl;
  1291. $a.setAttribute(RedirectApp.REDIRECT_COMPLETED, "true");
  1292. }
  1293. });
  1294. },
  1295. },
  1296. {
  1297. name: "菁优网",
  1298. urlTest: /www\.jyeoo\.com/,
  1299. linkTest: true,
  1300. onInit: function () {},
  1301. resolveRedirect: function (element) {},
  1302. },
  1303. ];
  1304. }
  1305.  
  1306. const autoJumpApp = new AutoJumpApp();
  1307. const autoJumpResult = autoJumpApp.bootstrap();
  1308. if (autoJumpResult) return;
  1309.  
  1310. const redirectApp = new RedirectApp();
  1311. redirectApp.bootstrap();
  1312. })();

QingJ © 2025

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