去除链接重定向

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

Від 09.08.2024. Дивіться остання версія.

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