open the link directly

点击链接直接跳转

当前为 2022-09-02 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name open the link directly
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1.8
  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. // https://www.jianshu.com/p/a3af2fb4d99e
  250. // https://link.jianshu.com/?t=http://www.ihie.org/john-kansky
  251. jianshu3: { pattern: /https?:\/\/link\.jianshu\.com\/\?t=(.+)$/ },
  252.  
  253. // href="https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.aliyun.com%2Fgroup%2Falisoftwaretech%2F"
  254. juejin: { pattern: /https?:\/\/link\.juejin\.cn\/?\?target=(.+)$/ },
  255.  
  256. // https://gitee.com/meetqy/acss-dnd
  257. // href="https://gitee.com/link?target=https%3A%2F%2Fcuyang.me%2Facss-dnd%2F"
  258. // https://blog.gitee.com/2022/01/20/lowcodetools/
  259. // https://link.juejin.cn/?target=https%3A%2F%2Flink.zhihu.com%2F%3Ftarget%3Dhttps%253A%2F%2Fgitee.com%2Fnocobase%2Fnocobase
  260. gitee: { pattern: /https?:\/\/gitee\.com\/link\?target=(.+)$/ },
  261.  
  262. // https://www.uisdc.com/build-b-end-grid-system
  263. // https://link.uisdc.com/?redirect=https%3A%2F%2Fuxdesign.cc%2Fresponsive-grids-and-how-to-actually-use-them-970de4c16e01
  264. uisdc: { pattern: /https?:\/\/link\.uisdc\.com\/?\?redirect=(.+)$/ },
  265.  
  266. // https://www.logonews.cn/apple-sues-sex-topic-blog-for-logo-infringement.html
  267. // https://link.logonews.cn/?url=http://aasd.k12.wi.us/district
  268. logonews: { pattern: /https?:\/\/link\.logonews\.cn\/?\?url=(.+)$/ },
  269.  
  270. // https://afdian.net/@AdventCirno
  271. // https://afdian.net/link?target=https%3A%2F%2Fwww.patreon.com%2Fuser%3Fu%3D6139561
  272. afdian: { pattern: /https?:\/\/afdian\.net\/link\?target=(.+)$/ },
  273.  
  274. // https://www.pixiv.net/users/25237
  275. // href="/jump.php?url=https%3A%2F%2Ftwitter.com%2Fomiya_io"
  276. // href="/jump.php?https%3A%2F%2Finstagram.com%2Fsnatti89%2F"
  277. pixiv: { pattern: /https?:\/\/www\.pixiv\.net\/jump\.php\?(?:url=)?(.+)$/ },
  278.  
  279. // https://my.oschina.net/androiddevs/blog/5496556
  280. // 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
  281. // 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"
  282. oschina: { pattern: /https?:\/\/www\.oschina\.net\/action\/GoToLink\?url=(.+)$/ },
  283.  
  284.  
  285. // https://uiiiuiii.com/software/491152.html
  286. // https://link.uiiiuiii.com/?redirect=https%3A%2F%2Fwww.behance.net%2Fgallery%2F117044741%2FHARSHITA-FREE-SIGNATURE-FONT%3Ftracking_source%3Dfor_you_feed_recommended
  287. uiiiuiii: { pattern: /https?:\/\/link\.uiiiuiii\.com\/?\?redirect=(.+)$/ },
  288.  
  289. // https://leetcode-cn.com/circle/discuss/mL0gxC/
  290. // https://leetcode-cn.com/link/?target=http%3A%2F%2Fwww.bytedance.com
  291. leetcodecn: { pattern: /https?:\/\/leetcode-cn\.com\/link\/\?target=(.+)$/ },
  292.  
  293. // https://huaban.com/pins/4614750040
  294. // https://huaban.com/go?pin_id=4614749616
  295. huaban: { pattern: /https?:\/\/huaban\.com\/go\?pin_id=(.+)$/, resolver: huabanResolver },
  296.  
  297. // https://weibo.cn/sinaurl?u=https%3A%2F%2Fwww.freebsd.org%2F
  298. // https://weibo.cn/sinaurl?toasturl=https%3A%2F%2Ftime.geekbang.org%2F
  299. // https://weibo.cn/sinaurl?luicode=10000011&lfid=230259&u=http%3A%2F%2Ft.cn%2FA6qHeVlf
  300. // https://weibo.cn/sinaurl?f=w&u=http%3A%2F%2Ft.cn%2FA66XY2gI&ep=LlAsNz3HD%2C1683963007%2CLlpkandl6%2C7276218544
  301. // href="https://weibo.cn/sinaurl?f=w&u=http%3A%2F%2Ft.cn%2FA66XY2gI&ep=LlAsNz3HD%2C1683963007%2CLlpkandl6%2C7276218544"
  302. weibo: { pattern: /https?:\/\/weibo\.cn\/sinaurl\?[f=w&]?u=(.+)$/, resolver: weiboResolver },
  303.  
  304. // http://t.cn/A66926Pm 未备案的, 跳转到中转网址, response.finalUrl仍然还是http://t.cn/A66926Pm 目标网址出现在response.responseText里
  305. // http://t.cn/A669K964 已备案的, 直接跳转到目标网址, 出现在response.finalUrl里
  306. weibo2: { pattern: /(https?:\/\/t\.cn\/.+)$/, resolver: weiboResolver },
  307.  
  308.  
  309. // segmentfault对链接进行加密处理, 不知道如何decode, 所以只能写一个函数去单独处理
  310. // https://link.segmentfault.com/?enc=LZyRulLABKpXOHl2vbA%2F4w%3D%3D.MWhFMvjhyBk1ReIRoGxyxa0VxGtg%2Foyk0DMtfzZTJoKbsgoJFtGCPHe8%2BZ1HbRdcvNsGaVfll9oGQXLsZCHK7w%3D%3D
  311. // https://segmentfault.com/a/1190000017434150
  312. segfault: { pattern: /https?:\/\/link\.segmentfault\.com\/?\?enc=(.+)$/, resolver: segfaultResolver },
  313.  
  314. // https://www.bookmarkearth.com/detail/097c687c98974691b2174bc1e85103d4
  315. // https://show.bookmarkearth.com/view/801
  316. bookmarkearth: { pattern: /(https?:\/\/show\.bookmarkearth\.com\/view\/.+)$/, resolver: bmeResolver },
  317.  
  318.  
  319. // 以下网站a标签的herf未修改, 推测是js做的弹窗, 所以不需要匹配, 也匹配不出来
  320. // csdn https://link.csdn.net/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FRegExp
  321. // 语雀 https://www.yuque.com/r/goto?url=https%3A%2F%2Fwww.canva.cn%2F
  322.  
  323. // https://blog.csdn.net/weixin_41010294/article/details/85289852
  324. csdn: { host: 'csdn.net' },
  325.  
  326. // https://www.yuque.com/yuque/gpvawt/fuu6h3
  327. yuque: { host: 'yuque.com' },
  328.  
  329. // https://www.tianyancha.com/company/28723141
  330. // href="https://ss.knet.cn/verifyseal.dll?sn=e18042711010873571xsuv000000&pa=111332"
  331. // https://www.tianyancha.com/security?target=https%3A%2F%2Fss.knet.cn%2Fverifyseal.dll%3Fsn%3De18042711010873571xsuv000000%26pa%3D111332
  332. tianyancha: { host: 'tianyancha.com' },
  333.  
  334. }
  335.  
  336. const matchHostResolver = (url, host) => {
  337. // 1. 需要当前网站与host匹配
  338. if (!window.location.host.includes(host)) {
  339. return false;
  340. }
  341. // 2. url为外链
  342. if (url.host.includes(host)) {
  343. return false;
  344. }
  345.  
  346. return true;
  347. }
  348.  
  349. const getMatchPattern = (url) => {
  350. for (let i in patter_match) {
  351.  
  352. if (patter_match[i].hasOwnProperty('host') && matchHostResolver(url, patter_match[i].host)) {
  353. return patter_match[i];
  354. }
  355. if (patter_match[i].hasOwnProperty('pattern') && url.href.match(patter_match[i].pattern)) {
  356. return patter_match[i];
  357. }
  358. }
  359. return null;
  360. }
  361.  
  362. const resolveRealURI = async (href) => {
  363. // TODO 兼容套娃链接, 如 https://link.juejin.cn/?target=https%3A%2F%2Flink.zhihu.com%2F%3Ftarget%3Dhttps%253A%2F%2Fgitee.com%2Fnocobase%2Fnocobase
  364. const fallbackURI = href;
  365.  
  366. for (let i in patter_match) {
  367. const matcher = href.match(patter_match[i].pattern);
  368. if (!matcher) {
  369. continue;
  370. }
  371. console.log({ matcher });
  372.  
  373. if (patter_match[i].hasOwnProperty('resolver')) {
  374. // complex customize resolver
  375. console.log(patter_match[i].resolver)
  376. let realURI = await patter_match[i].resolver(href);
  377. return realURI;
  378. }
  379.  
  380. const encodeURI = matcher[1];
  381. // simple reg resolver
  382. return decodeURIComponent(encodeURI);
  383. }
  384. return fallbackURI;
  385. }
  386.  
  387. const patternResolve = async (patter_match, href) => {
  388.  
  389. if (patter_match.hasOwnProperty('resolver')) {
  390. // complex customize resolver
  391. console.log(patter_match.resolver)
  392. let realURI = await patter_match.resolver(href);
  393. return realURI;
  394. }
  395.  
  396. if (patter_match.hasOwnProperty('pattern')) {
  397. const matcher = href.match(patter_match.pattern);
  398. if (!matcher) { return href; }
  399. const encodeURI = matcher[1];
  400. return decodeURIComponent(encodeURI);
  401. }
  402.  
  403. return href;
  404. }
  405.  
  406. const getAnchorElement = (e) => {
  407. let target = e.target;
  408. while (target) {
  409. if (target.tagName.toLowerCase() === 'a' && target.hasAttribute('href')) {
  410. return target;
  411. }
  412. target = target.parentElement;
  413. }
  414. return null;
  415. }
  416.  
  417. document.addEventListener('click', (e) => {
  418. console.log({ e })
  419.  
  420. // 找到a标签
  421. let anchor = getAnchorElement(e);
  422. if (!anchor) {
  423. return;
  424. }
  425.  
  426. console.log({ anchor })
  427.  
  428. let href = anchor.getAttribute('href');
  429. if (!href) {
  430. return;
  431. }
  432.  
  433. // 不是url, 则不做处理
  434. // 兼容如 href设置为 'javascript:void(0);' 等的情况
  435. let url = getValidURL(href);
  436. if (url === null) {
  437. return;
  438. }
  439.  
  440. if (!isHttpProtocol(url)) {
  441. return;
  442. }
  443.  
  444. console.log({ url });
  445.  
  446. const matchPattern = getMatchPattern(url);
  447. console.log({ matchPattern });
  448. if (!matchPattern) {
  449. console.log("不匹配处理规则, 如有误, 请反馈给我, 非常感谢");
  450. return;
  451. }
  452.  
  453. e.stopPropagation();
  454. e.preventDefault();
  455.  
  456. let target = '_self';
  457. if (anchor.hasAttribute('target')) {
  458. target = anchor.getAttribute('target');
  459. }
  460.  
  461. // csdn 打开新标签页兼容处理
  462. if (matchPattern == patter_match.csdn) {
  463. target = '_blank';
  464. }
  465.  
  466. console.log({ target });
  467. patternResolve(matchPattern, url.href).then((realURI) => {
  468. console.log({ realURI });
  469. window.open(realURI, target);
  470. }).catch(() => { window.open(href, target); });
  471. }, { capture: true })
  472.  
  473. })();