open the link directly

点击链接直接跳转

当前为 2022-03-29 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name open the link directly
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1.4
  5. // @description 点击链接直接跳转
  6. // @author nediiii
  7. // @match *://*.csdn.net/*
  8. // @match *://*.gitee.com/*
  9. // @match *://*.uisdc.com/*
  10. // @match *://*.uiiiuiii.com/*
  11. // @match *://*.logonews.cn/*
  12. // @match *://*.afdian.net/*
  13. // @match *://*.tianyancha.com/*
  14. // @match *://*.oschina.net/*
  15. // @match *://*.pixiv.net/*
  16. // @match *://*.jianshu.com/*
  17. // @match *://*.juejin.cn/*
  18. // @match *://*.weibo.cn/*
  19. // @match *://*.weibo.com/*
  20. // @match *://*.yuque.com/*
  21. // @match *://*.segmentfault.com/*
  22. // @match *://*.zhihu.com/*
  23. // @match *://*.bookmarkearth.com/*
  24. // @run-at document-start
  25. // @license GPLv3 License
  26. // @icon https://www.google.com/s2/favicons?sz=64&domain=greasyfork.org
  27. // @grant GM_xmlhttpRequest
  28. // @grant unsafeWindow
  29. // ==/UserScript==
  30.  
  31. (function () {
  32. 'use strict';
  33.  
  34. // 保存原始方法
  35. unsafeWindow.__otld_open = unsafeWindow.open;
  36. // 重写 open 方法
  37. var myopen = function (url, name, features) {
  38. console.log({ url }, { name }, { features });
  39. // debugger;
  40. return unsafeWindow.__otld_open(url, name, features);
  41. }
  42. // 屏蔽 JS 中对原生函数 native 属性的检测
  43. var _myopen = myopen.bind(null);
  44. _myopen.toString = unsafeWindow.__otld_open.toString;
  45. Object.defineProperty(unsafeWindow, 'open', {
  46. value: _myopen
  47. });
  48.  
  49. const getValidURL = (url) => {
  50. try {
  51. let u = new URL(url);
  52. return u;
  53. } catch (error) {
  54. return getValidURLWithBase(url);
  55. }
  56. }
  57.  
  58. const getValidURLWithBase = (url) => {
  59. try {
  60. let u = new URL(url, getCurrentURLBase());
  61. return u;
  62. } catch (error) {
  63. return null;
  64. }
  65. }
  66.  
  67. const getCurrentURLBase = () => {
  68. return window.location.origin;
  69. }
  70.  
  71. const urlReg = /\bhttps?:\/\/\S+/gi;
  72.  
  73. const weiboResolver = async (href) => {
  74. return new Promise((resolve, reject) => {
  75. GM_xmlhttpRequest({
  76. method: "GET",
  77. url: href,
  78. onload: function (response) {
  79. console.log({ response });
  80. if (response.status != 200) {
  81. reject(href);
  82. return;
  83. }
  84.  
  85. // weibo跳转会区分目标网址是否备案
  86. // 未备案的, 中转
  87. // 已备案的, 直跳
  88. let realURI = href;
  89. if (response.finalUrl === href) {
  90. // 未备案, 中转网址
  91. // 网址在html里
  92. let doc = new DOMParser().parseFromString(response.responseText, "text/html");
  93. let node = doc.querySelector('body > div > div:nth-child(2)');
  94. let str = node.innerText;
  95. console.log({ doc });
  96. console.log({ node });
  97. console.log({ str });
  98.  
  99. let extractURL = str.match(urlReg);
  100. console.log({ extractURL });
  101. if (extractURL) {
  102. realURI = extractURL[0];
  103. }
  104. }
  105. else {
  106. // 已备案
  107. // 网址在finalUrl里
  108. let extractURL = response.finalUrl.match(urlReg);
  109. if (extractURL) {
  110. realURI = extractURL[0];
  111. }
  112. }
  113.  
  114. resolve(realURI)
  115. },
  116. onerror: function (error) {
  117. console.log({ error });
  118. reject(href)
  119. }
  120. });
  121. });
  122. }
  123.  
  124. const segfaultResolver = async (href) => {
  125. return new Promise((resolve, reject) => {
  126. GM_xmlhttpRequest({
  127. method: "GET",
  128. url: href,
  129. onload: function (response) {
  130. console.log({ response });
  131. if (response.status == 200) {
  132. let doc = new DOMParser().parseFromString(response.responseText, "text/html");
  133. let node = doc.querySelector('body > p')
  134. let str = node.innerText;
  135. console.log({ doc });
  136. console.log({ node });
  137. console.log({ str });
  138.  
  139. let realURI = href;
  140. let extractURL = str.match(urlReg);
  141. console.log({ extractURL });
  142. if (extractURL) {
  143. realURI = extractURL[0];
  144. }
  145. resolve(realURI)
  146. } else {
  147. reject(href)
  148. }
  149. },
  150. onerror: function (error) {
  151. console.log({ error });
  152. reject(href)
  153. }
  154. });
  155. });
  156. }
  157.  
  158. const bmeResolver = async (href) => {
  159. return new Promise((resolve, reject) => {
  160. GM_xmlhttpRequest({
  161. method: "GET",
  162. url: href,
  163. onload: function (response) {
  164. console.log({ response });
  165. if (response.status == 200) {
  166. let doc = new DOMParser().parseFromString(response.responseText, "text/html");
  167. let node = doc.querySelector('body > div.row.box > div.col-lg-6.jump-box > div > div.content > p.link');
  168. let str = node.innerText;
  169. console.log({ doc });
  170. console.log({ node });
  171. console.log({ str });
  172.  
  173. let realURI = href;
  174. let extractURL = str.match(urlReg);
  175. console.log({ extractURL });
  176. if (extractURL) {
  177. realURI = extractURL[0];
  178. }
  179. resolve(realURI)
  180. } else {
  181. reject(href)
  182. }
  183. },
  184. onerror: function (error) {
  185. console.log({ error });
  186. reject(href)
  187. }
  188. });
  189. });
  190. }
  191.  
  192. const patter_match = {
  193.  
  194. // 注意这里的pattern需要去看对应网站dom里的a标签的实际herf值, console也会打印日志, 可以自己添加正则来增加网站支持
  195.  
  196. // https://zhuanlan.zhihu.com/p/23333042
  197. // https://link.zhihu.com/?target=https%3A//getkap.co/
  198. // https://link.zhihu.com/?target=https%3A//greasyfork.org/en/scripts/5029-yet-another-%25E8%2587%25AA%25E5%258F%25A4cb%25E5%2587%25BA%25E8%25AF%2584%25E8%25AE%25BA-sharing-plugin
  199. zhihu: { pattern: /https?:\/\/link\.zhihu\.com\/?\?target=(.+)$/ },
  200.  
  201. // https://www.jianshu.com/p/a6a63a0c6e53
  202. // https://links.jianshu.com/go?to=https%3A%2F%2Fnpm.taobao.org%2Fmirrors%2Felectron
  203. jianshu2: { pattern: /https?:\/\/links\.jianshu\.com\/go\?to=(.+)$/ },
  204.  
  205. // href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.aliyun.com%2Fgroup%2Falisoftwaretech%2F"
  206. juejin: { pattern: /https?:\/\/link\.juejin\.cn\/?\?target=(.+)$/ },
  207.  
  208. // https://gitee.com/meetqy/acss-dnd
  209. // href="https://gitee.com/link?target=https%3A%2F%2Fcuyang.me%2Facss-dnd%2F"
  210. // https://blog.gitee.com/2022/01/20/lowcodetools/
  211. // https://link.juejin.cn/?target=https%3A%2F%2Flink.zhihu.com%2F%3Ftarget%3Dhttps%253A%2F%2Fgitee.com%2Fnocobase%2Fnocobase
  212. gitee: { pattern: /https?:\/\/gitee\.com\/link\?target=(.+)$/ },
  213.  
  214. // https://www.uisdc.com/build-b-end-grid-system
  215. // https://link.uisdc.com/?redirect=https%3A%2F%2Fuxdesign.cc%2Fresponsive-grids-and-how-to-actually-use-them-970de4c16e01
  216. uisdc: { pattern: /https?:\/\/link\.uisdc\.com\/?\?redirect=(.+)$/ },
  217.  
  218. // https://www.logonews.cn/apple-sues-sex-topic-blog-for-logo-infringement.html
  219. // https://link.logonews.cn/?url=http://aasd.k12.wi.us/district
  220. logonews: { pattern: /https?:\/\/link\.logonews\.cn\/?\?url=(.+)$/ },
  221.  
  222. // https://afdian.net/@AdventCirno
  223. // https://afdian.net/link?target=https%3A%2F%2Fwww.patreon.com%2Fuser%3Fu%3D6139561
  224. afdian: { pattern: /https?:\/\/afdian\.net\/link\?target=(.+)$/ },
  225.  
  226. // https://www.pixiv.net/users/25237
  227. // href="/jump.php?url=https%3A%2F%2Ftwitter.com%2Fomiya_io"
  228. // href="/jump.php?https%3A%2F%2Finstagram.com%2Fsnatti89%2F"
  229. pixiv: { pattern: /https?:\/\/www\.pixiv\.net\/jump\.php\?(?:url=)?(.+)$/ },
  230.  
  231. // https://my.oschina.net/androiddevs/blog/5496556
  232. // https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fmp.weixin.qq.com%2Fmp%2Fappmsgalbum%3F__biz%3DMzk0NDIwMTExNw%3D%3D%26action%3Dgetalbum%26album_id%3D1879128471667326981%23wechat_redirect
  233. // href="https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fmp.weixin.qq.com%2Fmp%2Fappmsgalbum%3F__biz%3DMzk0NDIwMTExNw%3D%3D%26action%3Dgetalbum%26album_id%3D1879128471667326981%23wechat_redirect"
  234. oschina: { pattern: /https?:\/\/www\.oschina\.net\/action\/GoToLink\?url=(.+)$/ },
  235.  
  236.  
  237. // https://uiiiuiii.com/software/491152.html
  238. // https://link.uiiiuiii.com/?redirect=https%3A%2F%2Fwww.behance.net%2Fgallery%2F117044741%2FHARSHITA-FREE-SIGNATURE-FONT%3Ftracking_source%3Dfor_you_feed_recommended
  239. uiiiuiii: { pattern: /https?:\/\/link\.uiiiuiii\.com\/?\?redirect=(.+)$/ },
  240.  
  241. // https://weibo.cn/sinaurl?u=https%3A%2F%2Fwww.freebsd.org%2F
  242. // https://weibo.cn/sinaurl?toasturl=https%3A%2F%2Ftime.geekbang.org%2F
  243. // https://weibo.cn/sinaurl?luicode=10000011&lfid=230259&u=http%3A%2F%2Ft.cn%2FA6qHeVlf
  244. // https://weibo.cn/sinaurl?f=w&u=http%3A%2F%2Ft.cn%2FA66XY2gI&ep=LlAsNz3HD%2C1683963007%2CLlpkandl6%2C7276218544
  245. // href="https://weibo.cn/sinaurl?f=w&u=http%3A%2F%2Ft.cn%2FA66XY2gI&ep=LlAsNz3HD%2C1683963007%2CLlpkandl6%2C7276218544"
  246. weibo: { pattern: /https?:\/\/weibo\.cn\/sinaurl\?f=w&u=(.+)$/, resolver: weiboResolver },
  247.  
  248. // http://t.cn/A66926Pm 未备案的, 跳转到中转网址, response.finalUrl仍然还是http://t.cn/A66926Pm 目标网址出现在response.responseText里
  249. // http://t.cn/A669K964 已备案的, 直接跳转到目标网址, 出现在response.finalUrl里
  250. weibo2: { pattern: /(https?:\/\/t\.cn\/.+)$/, resolver: weiboResolver },
  251.  
  252.  
  253. // segmentfault对链接进行加密处理, 不知道如何decode, 所以只能写一个函数去单独处理
  254. // https://link.segmentfault.com/?enc=LZyRulLABKpXOHl2vbA%2F4w%3D%3D.MWhFMvjhyBk1ReIRoGxyxa0VxGtg%2Foyk0DMtfzZTJoKbsgoJFtGCPHe8%2BZ1HbRdcvNsGaVfll9oGQXLsZCHK7w%3D%3D
  255. // https://segmentfault.com/a/1190000017434150
  256. segfault: { pattern: /https?:\/\/link\.segmentfault\.com\/?\?enc=(.+)$/, resolver: segfaultResolver },
  257.  
  258. // https://www.bookmarkearth.com/detail/097c687c98974691b2174bc1e85103d4
  259. // https://show.bookmarkearth.com/view/801
  260. bookmarkearth: { pattern: /(https?:\/\/show\.bookmarkearth\.com\/view\/.+)$/, resolver: bmeResolver },
  261.  
  262.  
  263. // 以下网站a标签的herf未修改, 推测是js做的弹窗, 所以不需要匹配, 也匹配不出来
  264. // csdn https://link.csdn.net/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FRegExp
  265. // 语雀 https://www.yuque.com/r/goto?url=https%3A%2F%2Fwww.canva.cn%2F
  266.  
  267. // https://blog.csdn.net/weixin_41010294/article/details/85289852
  268. csdn: { host: 'csdn.net' },
  269.  
  270. // https://www.yuque.com/yuque/gpvawt/fuu6h3
  271. yuque: { host: 'yuque.com' },
  272.  
  273. // https://www.tianyancha.com/company/28723141
  274. // href="https://ss.knet.cn/verifyseal.dll?sn=e18042711010873571xsuv000000&pa=111332"
  275. // https://www.tianyancha.com/security?target=https%3A%2F%2Fss.knet.cn%2Fverifyseal.dll%3Fsn%3De18042711010873571xsuv000000%26pa%3D111332
  276. tianyancha: { host: 'tianyancha.com' },
  277.  
  278. }
  279.  
  280. const matchHostResolver = (url, host) => {
  281. // 1. 需要当前网站与host匹配
  282. if (!window.location.host.includes(host)) {
  283. return false;
  284. }
  285. // 2. url为外链
  286. if (url.host.includes(host)) {
  287. return false;
  288. }
  289.  
  290. return true;
  291. }
  292.  
  293. const getMatchPattern = (url) => {
  294. for (let i in patter_match) {
  295.  
  296. if (patter_match[i].hasOwnProperty('host') && matchHostResolver(url, patter_match[i].host)) {
  297. return patter_match[i];
  298. }
  299. if (patter_match[i].hasOwnProperty('pattern') && url.href.match(patter_match[i].pattern)) {
  300. return patter_match[i];
  301. }
  302. }
  303. return null;
  304. }
  305.  
  306. const resolveRealURI = async (href) => {
  307. // TODO 兼容套娃链接, 如 https://link.juejin.cn/?target=https%3A%2F%2Flink.zhihu.com%2F%3Ftarget%3Dhttps%253A%2F%2Fgitee.com%2Fnocobase%2Fnocobase
  308. const fallbackURI = href;
  309.  
  310. for (let i in patter_match) {
  311. const matcher = href.match(patter_match[i].pattern);
  312. if (!matcher) {
  313. continue;
  314. }
  315. console.log({ matcher });
  316.  
  317. if (patter_match[i].hasOwnProperty('resolver')) {
  318. // complex customize resolver
  319. console.log(patter_match[i].resolver)
  320. let realURI = await patter_match[i].resolver(href);
  321. return realURI;
  322. }
  323.  
  324. const encodeURI = matcher[1];
  325. // simple reg resolver
  326. return decodeURIComponent(encodeURI);
  327. }
  328. return fallbackURI;
  329. }
  330.  
  331. const patternResolve = async (patter_match, href) => {
  332.  
  333. if (patter_match.hasOwnProperty('resolver')) {
  334. // complex customize resolver
  335. console.log(patter_match.resolver)
  336. let realURI = await patter_match.resolver(href);
  337. return realURI;
  338. }
  339.  
  340. if (patter_match.hasOwnProperty('pattern')) {
  341. const matcher = href.match(patter_match.pattern);
  342. if (!matcher) { return href; }
  343. const encodeURI = matcher[1];
  344. return decodeURIComponent(encodeURI);
  345. }
  346.  
  347. return href;
  348. }
  349.  
  350. const getAnchorElement = (e) => {
  351. let target = e.target;
  352. while (target) {
  353. if (target.tagName.toLowerCase() === 'a' && target.hasAttribute('href')) {
  354. return target;
  355. }
  356. target = target.parentElement;
  357. }
  358. return null;
  359. }
  360.  
  361. document.addEventListener('click', (e) => {
  362. console.log({ e })
  363.  
  364. // 找到a标签
  365. let anchor = getAnchorElement(e);
  366. if (!anchor) {
  367. return;
  368. }
  369.  
  370. console.log({ anchor })
  371.  
  372. let href = anchor.getAttribute('href');
  373. if (!href) {
  374. return;
  375. }
  376.  
  377. // 不是url, 则不做处理
  378. // 兼容如 href设置为 'javascript:void(0);' 等的情况
  379. let url = getValidURL(href);
  380. if (url === null) {
  381. return;
  382. }
  383.  
  384. console.log({ url });
  385.  
  386. const matchPattern = getMatchPattern(url);
  387. console.log({ matchPattern });
  388. if (!matchPattern) {
  389. console.log("不匹配处理规则, 如有误, 请反馈给我, 非常感谢");
  390. return;
  391. }
  392.  
  393. e.stopPropagation();
  394. e.preventDefault();
  395.  
  396. let target = '_self';
  397. if (anchor.hasAttribute('target')) {
  398. target = anchor.getAttribute('target');
  399. }
  400.  
  401. // csdn 打开新标签页兼容处理
  402. if (matchPattern == patter_match.csdn) {
  403. target = '_blank';
  404. }
  405.  
  406. console.log({ target });
  407. patternResolve(matchPattern, url.href).then((realURI) => {
  408. console.log({ realURI });
  409. window.open(realURI, target);
  410. }).catch(() => { window.open(href, target); });
  411. }, { capture: true })
  412.  
  413. })();