open the link directly

点击链接直接跳转

当前为 2022-04-14 提交的版本,查看 最新版本

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