去除链接重定向

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

La data de 01-08-2024. Vezi ultima versiune.

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