redirect 外链跳转

自动跳转(重定向)到目标链接,免去点击步骤。适配了简书、知乎、微博、QQ邮箱、QQPC、印象笔记、贴吧、CSDN、YouTube、微信、微信开放社区、开发者知识库、豆瓣、个人图书馆、Pixiv、搜狗、Google、站长之家、OSCHINA、掘金、腾讯文档、pc6下载站、爱发电、Gitee、天眼查、爱企查、企查查、优设网、51CTO、力扣

目前为 2022-09-07 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name redirect 外链跳转
  3. // @version 1.31.0
  4. // @description 自动跳转(重定向)到目标链接,免去点击步骤。适配了简书、知乎、微博、QQ邮箱、QQPC、印象笔记、贴吧、CSDN、YouTube、微信、微信开放社区、开发者知识库、豆瓣、个人图书馆、Pixiv、搜狗、Google、站长之家、OSCHINA、掘金、腾讯文档、pc6下载站、爱发电、Gitee、天眼查、爱企查、企查查、优设网、51CTO、力扣
  5. // @author sakura-flutter
  6. // @namespace https://github.com/sakura-flutter/tampermonkey-scripts
  7. // @license GPL-3.0
  8. // @compatible chrome Latest
  9. // @compatible firefox Latest
  10. // @compatible edge Latest
  11. // @run-at document-start
  12. // @match *://www.jianshu.com/go-wild*
  13. // @match *://link.zhihu.com/*
  14. // @match *://t.cn/*
  15. // @match *://weibo.cn/sinaurl*
  16. // @match *://mail.qq.com/cgi-bin/*
  17. // @match *://c.pc.qq.com/middlem.html*
  18. // @match *://app.yinxiang.com/OutboundRedirect.action*
  19. // @match *://jump.bdimg.com/safecheck/*
  20. // @match *://jump2.bdimg.com/safecheck/*
  21. // @match *://link.csdn.net/*
  22. // @match *://www.youtube.com/redirect*
  23. // @match *://weixin110.qq.com/cgi-bin/mmspamsupport-bin/newredirectconfirmcgi*
  24. // @match *://developers.weixin.qq.com/community/middlepage/href*
  25. // @match *://www.itdaan.com/link/*
  26. // @match *://www.douban.com/link2/*
  27. // @match *://www.360doc.com/content/*
  28. // @match *://www.pixiv.net/jump.php*
  29. // @match *://m.sogou.com/*/tc*
  30. // @match *://m.sogou.com*/tc*
  31. // @match *://www.chinaz.com/go.shtml*
  32. // @match *://www.oschina.net/action/GoToLink*
  33. // @match *://link.juejin.cn/*
  34. // @match *://docs.qq.com/scenario/link.html*
  35. // @match *://www.pc6.com/goread.html*
  36. // @match *://afdian.net/link*
  37. // @match *://gitee.com/link*
  38. // @match *://www.tianyancha.com/security*
  39. // @match *://aiqicha.baidu.com/safetip*
  40. // @match *://www.qcc.com/web/transfer-link*
  41. // @match *://link.uisdc.com/*
  42. // @match *://blog.51cto.com/transfer*
  43. // @match *://leetcode.cn/link*
  44. // @include /^https?:\/\/www\.google\..{2,7}url/
  45. // ==/UserScript==
  46.  
  47. /******/ (() => { // webpackBootstrap
  48. /******/ "use strict";
  49. /******/ // The require scope
  50. /******/ var __webpack_require__ = {};
  51. /******/
  52. /************************************************************************/
  53. /******/ /* webpack/runtime/define property getters */
  54. /******/ (() => {
  55. /******/ // define getter functions for harmony exports
  56. /******/ __webpack_require__.d = (exports, definition) => {
  57. /******/ for(var key in definition) {
  58. /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
  59. /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
  60. /******/ }
  61. /******/ }
  62. /******/ };
  63. /******/ })();
  64. /******/
  65. /******/ /* webpack/runtime/hasOwnProperty shorthand */
  66. /******/ (() => {
  67. /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
  68. /******/ })();
  69. /******/
  70. /******/ /* webpack/runtime/make namespace object */
  71. /******/ (() => {
  72. /******/ // define __esModule on exports
  73. /******/ __webpack_require__.r = (exports) => {
  74. /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
  75. /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
  76. /******/ }
  77. /******/ Object.defineProperty(exports, '__esModule', { value: true });
  78. /******/ };
  79. /******/ })();
  80. /******/
  81. /************************************************************************/
  82. var __webpack_exports__ = {};
  83.  
  84. // NAMESPACE OBJECT: ./src/utils/ready-state.ts
  85. var ready_state_namespaceObject = {};
  86. __webpack_require__.r(ready_state_namespaceObject);
  87. __webpack_require__.d(ready_state_namespaceObject, {
  88. "DOMContentLoaded": () => (DOMContentLoaded),
  89. "complete": () => (complete),
  90. "interactive": () => (interactive),
  91. "load": () => (load),
  92. "loading": () => (loading)
  93. });
  94.  
  95. ;// CONCATENATED MODULE: ./src/utils/log.ts
  96. const isDebug = "production" !== 'production';
  97.  
  98. function warn(...args) {
  99. isDebug && warn.force(...args);
  100. }
  101.  
  102. warn.force = function (...args) {
  103. console.warn('%c warn ', 'background: #ffa500; padding: 1px; color: #fff;', ...args);
  104. };
  105.  
  106. function error(...args) {
  107. isDebug && error.force(...args);
  108. }
  109.  
  110. error.force = function (...args) {
  111. console.error('%c error ', 'background: red; padding: 1px; color: #fff;', ...args);
  112. };
  113.  
  114. function table(...args) {
  115. isDebug && console.table(...args);
  116. }
  117.  
  118.  
  119. ;// CONCATENATED MODULE: ./src/utils/ready-state.ts
  120. /**
  121. * readyState 因为脚本加载时机不一定监听到所有变化
  122. * 所以 pool 中的状态区分先后顺序
  123. * 靠后定义的会自动将靠前定义的但没有监听到的执行一次,但实际上不再是原来的状态
  124. */
  125.  
  126. const pool = new Map([['loading', []], ['interactive', []], ['DOMContentLoaded', []], // 扩展状态
  127. ['complete', []], ['load', []] // 扩展状态,不一定可以监听到
  128. ]);
  129. let currentState = document.readyState;
  130.  
  131. const execute = (readyState = currentState) => {
  132. currentState = readyState;
  133.  
  134. for (const [state, functions] of pool) {
  135. while (functions.length) {
  136. functions.shift()();
  137. }
  138.  
  139. if (readyState === state) break;
  140. }
  141. };
  142.  
  143. warn('document.readyState', currentState);
  144. execute();
  145.  
  146. if (document.readyState !== 'complete') {
  147. document.addEventListener('readystatechange', () => execute(document.readyState));
  148. window.addEventListener('DOMContentLoaded', () => execute('DOMContentLoaded'));
  149. }
  150.  
  151. window.addEventListener('load', () => execute('load'));
  152.  
  153. const wrapper = (readyState, fn) => new Promise(resolve => {
  154. pool.get(readyState).push(function () {
  155. resolve(fn?.());
  156. }); // 立即检查一下
  157.  
  158. execute();
  159. });
  160.  
  161. const loading = fn => wrapper('loading', fn);
  162. const interactive = fn => wrapper('interactive', fn);
  163. const DOMContentLoaded = fn => wrapper('DOMContentLoaded', fn);
  164. const complete = fn => wrapper('complete', fn);
  165. const load = fn => wrapper('load', fn);
  166. ;// CONCATENATED MODULE: ./src/utils/querystring.ts
  167. /**
  168. * 解析 query
  169. * @param href 或 带有参数格式的 string;有 search 则不再 hash
  170. */
  171. function parse(href = location.href) {
  172. if (!href) return {};
  173. let search;
  174.  
  175. try {
  176. // 链接
  177. const url = new URL(href);
  178. ({
  179. search
  180. } = url); // 主要处理对hash的search
  181.  
  182. if (!search && url.hash.includes('?')) {
  183. search = url.hash.split('?')[1];
  184. }
  185. } catch {
  186. // 非链接,如:a=1&b=2、?a=1、/foo?a=1、/foo#bar?a=1
  187. if (href.includes('?')) {
  188. search = href.split('?')[1];
  189. } else {
  190. search = href;
  191. }
  192. }
  193.  
  194. return Object.fromEntries(new URLSearchParams(search));
  195. }
  196. function stringify(obj) {
  197. return Object.entries(obj) // 过滤 undefined,保留 null 且转成 ''
  198. .filter(([, value]) => value !== undefined).map(([key, value]) => `${key}=${value ?? ''}`).join('&');
  199. }
  200. ;// CONCATENATED MODULE: ./src/utils/selector.ts
  201. const $ = document.querySelector.bind(document);
  202. const $$ = document.querySelectorAll.bind(document);
  203. ;// CONCATENATED MODULE: ./src/scripts/redirect/sites/t-cn.ts
  204.  
  205. const weibo = async () => {
  206. let link = $('.open-url a[href]')?.href;
  207. link || (link = await fetch(location.href).then(response => response.headers.get('location')));
  208. return {
  209. link
  210. };
  211. };
  212. ;// CONCATENATED MODULE: ./src/scripts/redirect/sites/weixin110-qq-com.ts
  213. /* eslint-disable camelcase */
  214.  
  215. const weixin = () => {
  216. const {
  217. main_type
  218. } = parse();
  219.  
  220. switch (main_type) {
  221. case '1':
  222. return {
  223. selector: '.weui-msg__text-area .ui-ellpisis-content p'
  224. };
  225.  
  226. case '2':
  227. {
  228. const url = new URL(location.href); // 转为1可还原链接
  229.  
  230. url.searchParams.set('main_type', '1');
  231. location.replace(url.href);
  232. }
  233. }
  234.  
  235. return {};
  236. };
  237. ;// CONCATENATED MODULE: ./src/scripts/redirect/sites/www-360doc-com.ts
  238.  
  239.  
  240. const doc360 = () => {
  241. $('#artContent').addEventListener('click', event => {
  242. const {
  243. target
  244. } = event;
  245. const href = target.href;
  246. warn(target);
  247. if (target.nodeName !== 'A') return;
  248. if (!href) return; // 是否本站
  249.  
  250. if (new RegExp(location.host).test(new URL(href).host)) return;
  251. event.stopPropagation();
  252. window.open(href);
  253. }, true);
  254. return {};
  255. };
  256. ;// CONCATENATED MODULE: ./src/scripts/redirect/sites/www-pixiv-net.ts
  257.  
  258. const pixiv = () => {
  259. let link; // 链接居然是直接拼在url上的
  260. // https://www.pixiv.net/jump.php?https%3A%2F%2Fwww.huawei.com%2Fcn%2Fcorporate-information
  261.  
  262. for (const [key, value] of Object.entries(parse())) {
  263. try {
  264. link || (link = new URL(key).href);
  265. } catch {}
  266.  
  267. try {
  268. link || (link = new URL(value).href);
  269. } catch {}
  270. }
  271.  
  272. return {
  273. link
  274. };
  275. };
  276. ;// CONCATENATED MODULE: ./src/scripts/redirect/sites/index.ts
  277.  
  278.  
  279.  
  280.  
  281.  
  282. const sites = [{
  283. name: '简书',
  284. test: 'www.jianshu.com/go-wild',
  285. use: () => ({
  286. query: 'url'
  287. })
  288. }, {
  289. name: '知乎',
  290. test: 'link.zhihu.com/',
  291. use: () => ({
  292. query: 'target'
  293. })
  294. }, {
  295. name: '微博',
  296. test: /^t\.cn\//,
  297. readyState: 'interactive',
  298. use: weibo
  299. }, {
  300. name: '微博',
  301. // 不同规则
  302. test: 'weibo.cn/sinaurl',
  303. use: () => ({
  304. link: parse().toasturl || parse().u
  305. })
  306. }, {
  307. name: 'QQ邮箱',
  308. test: [/^mail\.qq\.com\/cgi-bin\/readtemplate/, // 好像不用登录(不可用)也可以
  309. /^mail\.qq\.com\/cgi-bin\/mail_spam/ // 需要登录(不可用)邮箱才可以,不过这里仍然可以帮忙跳转
  310. ],
  311. use: () => ({
  312. link: parse().url || parse().gourl
  313. })
  314. }, {
  315. name: 'QQPC',
  316. test: 'c.pc.qq.com/middlem.html',
  317. use: () => ({
  318. query: 'pfurl'
  319. })
  320. }, {
  321. name: '腾讯文档',
  322. test: 'docs.qq.com/scenario/link.html',
  323. use: () => ({
  324. query: 'url'
  325. })
  326. }, {
  327. name: '印象笔记',
  328. test: /^app\.yinxiang\.com\/OutboundRedirect/,
  329. use: () => ({
  330. query: 'dest'
  331. })
  332. }, {
  333. name: '贴吧',
  334. test: /^jump2?\.bdimg\.com\/safecheck/,
  335. // 以前的地址没有 2
  336. readyState: 'interactive',
  337. use: () => ({
  338. selector: '.warning_info a:nth-of-type(1)[href]',
  339. attr: 'href'
  340. })
  341. }, {
  342. name: 'CSDN',
  343. test: 'link.csdn.net/',
  344. use: () => ({
  345. query: 'target'
  346. })
  347. }, {
  348. name: 'YouTube',
  349. test: 'www.youtube.com/redirect',
  350. use: () => ({
  351. query: 'q'
  352. })
  353. }, {
  354. name: '微信',
  355. test: /^weixin110\.qq\.com\/cgi-bin\/mmspamsupport-bin\/newredirectconfirmcgi/,
  356. readyState: 'interactive',
  357. use: weixin
  358. }, {
  359. name: '微信开放社区',
  360. test: 'developers.weixin.qq.com/community/middlepage/href',
  361. use: () => ({
  362. query: 'href'
  363. })
  364. }, {
  365. name: '开发者知识库',
  366. test: /^www\.itdaan.com\/link\//,
  367. readyState: 'interactive',
  368. use: () => ({
  369. selector: '.safety-url'
  370. })
  371. }, {
  372. name: '豆瓣',
  373. test: 'www.douban.com/link2/',
  374. use: () => ({
  375. query: 'url'
  376. })
  377. }, {
  378. name: '个人图书馆',
  379. test: /^www\.360doc.com\/content\//,
  380. readyState: 'interactive',
  381. use: doc360
  382. }, {
  383. name: 'Pixiv',
  384. test: 'www.pixiv.net/jump.php',
  385. use: pixiv
  386. }, {
  387. name: '搜狗',
  388. test: /^m\.sogou\.com.*tc$/,
  389. use: () => ({
  390. query: 'url'
  391. })
  392. }, {
  393. name: 'Google',
  394. test: /^www\.google\..{2,7}url$/,
  395. use: () => ({
  396. link: parse().url || parse().q
  397. })
  398. }, {
  399. name: '站长之家',
  400. test: 'www.chinaz.com/go.shtml',
  401. use: () => ({
  402. query: 'url'
  403. })
  404. }, {
  405. name: 'OSCHINA',
  406. test: 'www.oschina.net/action/GoToLink',
  407. use: () => ({
  408. query: 'url'
  409. })
  410. }, {
  411. name: '掘金',
  412. test: 'link.juejin.cn/',
  413. use: () => ({
  414. query: 'target'
  415. })
  416. }, {
  417. name: 'pc6下载站',
  418. test: 'www.pc6.com/goread.html',
  419. use: () => ({
  420. query: 'gourl'
  421. })
  422. }, {
  423. name: '爱发电',
  424. test: 'afdian.net/link',
  425. use: () => ({
  426. query: 'target'
  427. })
  428. }, {
  429. name: 'Gitee',
  430. test: 'gitee.com/link',
  431. use: () => ({
  432. query: 'target'
  433. })
  434. }, {
  435. name: '天眼查',
  436. test: 'www.tianyancha.com/security',
  437. use: () => ({
  438. query: 'target'
  439. })
  440. }, {
  441. name: '爱企查',
  442. test: 'aiqicha.baidu.com/safetip',
  443. use: () => ({
  444. query: 'target'
  445. })
  446. }, {
  447. name: '企查查',
  448. test: 'www.qcc.com/web/transfer-link',
  449. use: () => ({
  450. query: 'link'
  451. })
  452. }, {
  453. name: '优设网',
  454. test: 'link.uisdc.com/',
  455. use: () => ({
  456. query: 'redirect'
  457. })
  458. }, {
  459. name: '51CTO',
  460. test: 'blog.51cto.com/transfer',
  461. use: () => ({
  462. link: location.search.slice(1)
  463. })
  464. }, {
  465. name: '力扣',
  466. test: 'leetcode.cn/link/',
  467. use: () => ({
  468. query: 'target'
  469. })
  470. }];
  471. /* harmony default export */ const redirect_sites = (sites);
  472. ;// CONCATENATED MODULE: ./src/scripts/redirect/index.ts
  473. function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; }
  474.  
  475. var id = 0;
  476.  
  477. function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }
  478.  
  479.  
  480.  
  481.  
  482.  
  483.  
  484.  
  485. function hidePage() {
  486. interactive(() => {
  487. document.body.style.cssText = 'display:none !important;';
  488. });
  489. }
  490.  
  491. var _sites = /*#__PURE__*/_classPrivateFieldLooseKey("sites");
  492.  
  493. var _includes = /*#__PURE__*/_classPrivateFieldLooseKey("includes");
  494.  
  495. var _parse = /*#__PURE__*/_classPrivateFieldLooseKey("parse");
  496.  
  497. var _ensure = /*#__PURE__*/_classPrivateFieldLooseKey("ensure");
  498.  
  499. class App {
  500. constructor(sites) {
  501. Object.defineProperty(this, _ensure, {
  502. value: _ensure2
  503. });
  504. Object.defineProperty(this, _parse, {
  505. value: _parse2
  506. });
  507. Object.defineProperty(this, _includes, {
  508. value: _includes2
  509. });
  510. Object.defineProperty(this, _sites, {
  511. writable: true,
  512. value: void 0
  513. });
  514. _classPrivateFieldLooseBase(this, _sites)[_sites] = sites;
  515. }
  516.  
  517. boot() {
  518. const briefURL = location.host + location.pathname;
  519.  
  520. _classPrivateFieldLooseBase(this, _sites)[_sites].forEach(async site => {
  521. const {
  522. name,
  523. test,
  524. use
  525. } = site;
  526. if (!_classPrivateFieldLooseBase(this, _includes)[_includes](test, briefURL)) return;
  527. const {
  528. readyState: state
  529. } = site;
  530. if (state) await ready_state_namespaceObject[state]();
  531. const redirection = await _classPrivateFieldLooseBase(this, _parse)[_parse](use);
  532. table({
  533. name,
  534. briefURL,
  535. redirection
  536. });
  537. if (!redirection) return;
  538. location.replace(redirection); // 为什么要这样做?
  539. // 只是为了避免被问“哎!怎么好像没有跳转啊?!”的烦恼(实际上跳转了只是外链打开慢)(x_x)
  540.  
  541. hidePage();
  542. });
  543. }
  544.  
  545. }
  546.  
  547. function _includes2(test, url) {
  548. return [].concat(test).some(item => {
  549. if (typeof item === 'string') return item === url;
  550. if (item instanceof RegExp) return item.test(url);
  551. return false;
  552. });
  553. }
  554.  
  555. async function _parse2(use) {
  556. const {
  557. query,
  558. link,
  559. selector,
  560. attr
  561. } = await use();
  562. let redirection;
  563.  
  564. if (query) {
  565. redirection = parse()[query];
  566. } else if (link) {
  567. redirection = link;
  568. } else if (selector) {
  569. redirection = $(selector)?.[attr ?? 'innerText'];
  570. }
  571.  
  572. redirection && (redirection = _classPrivateFieldLooseBase(this, _ensure)[_ensure](redirection.trim()));
  573. return redirection;
  574. }
  575.  
  576. function _ensure2(url) {
  577. try {
  578. // eslint-disable-next-line no-new
  579. new URL(url);
  580. } catch (error) {
  581. warn(error); // 修复某些链接没有 protocol 导致跳转不正确
  582. // https://gf.qytechs.cn/zh-CN/scripts/416338-redirect-外链跳转/discussions/69178
  583.  
  584. const protocol = 'http:';
  585. url = protocol + '//' + url;
  586. }
  587.  
  588. return url;
  589. }
  590.  
  591. new App(redirect_sites).boot();
  592. /******/ })()
  593. ;

QingJ © 2025

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