Decloak links and open directly

Open redirected/cloaked links directly

当前为 2021-03-27 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Decloak links and open directly
  3. // @description Open redirected/cloaked links directly
  4. // @version 2.2.0
  5. // @author wOxxOm
  6. // @namespace wOxxOm.scripts
  7. // @icon https://i.imgur.com/cfmXJHv.png
  8. // @resource icon https://i.imgur.com/cfmXJHv.png
  9. // @license MIT License
  10. // @run-at document-start
  11. // @grant GM_getResourceURL
  12. // @match *://*/*
  13. // ==/UserScript==
  14.  
  15. 'use strict';
  16.  
  17. const POPUP = document.createElement('a');
  18. POPUP.id = GM_info.script.name;
  19. POPUP.title = 'Original link';
  20. let isPopupStyled;
  21. let lastLink;
  22. let hoverTimer;
  23. let hoverStopTimer;
  24.  
  25. addEventListener('keypress', e => e.which === 13 && decloakLink(e), true);
  26. addEventListener('mousedown', decloakLink, true);
  27. addEventListener('mouseover', onHover, true);
  28.  
  29. function onHover(event) {
  30. const a = decloakLink(event);
  31. if (a) {
  32. if (lastLink)
  33. lastLink.removeEventListener('mouseout', cancelHover);
  34. clearTimeout(hoverTimer);
  35. hoverTimer = setTimeout(showPopup, 500, a);
  36. a.addEventListener('mouseout', cancelHover);
  37. }
  38. lastLink = a;
  39. }
  40.  
  41. function cancelHover(e) {
  42. this.removeEventListener('mouseout', cancelHover);
  43. clearTimeout(hoverStopTimer);
  44. hoverStopTimer = setTimeout(hidePopup, 500, this);
  45. }
  46.  
  47. function showPopup(a) {
  48. if (!a.matches(':hover'))
  49. return;
  50. if (!isPopupStyled) {
  51. isPopupStyled = true;
  52. POPUP.style.cssText = //'all: unset;' +
  53. 'width: 18px;' +
  54. 'height: 18px;' +
  55. 'background: url("' + GM_getResourceURL('icon') + '") center no-repeat, white;' +
  56. 'background-size: 16px;' +
  57. 'opacity: 0;' +
  58. 'transition: opacity .5s;' +
  59. 'border: 1px solid #888;' +
  60. 'border-radius: 11px;' +
  61. 'z-index: 2147483647;' +
  62. 'margin-left: 0;' +
  63. 'cursor: pointer;' +
  64. 'position: absolute;'
  65. .replace(/;/g, '!important;');
  66. }
  67. const linkStyle = getComputedStyle(a);
  68. POPUP.href = a.hrefUndecloaked;
  69. POPUP.style.opacity = '0';
  70. POPUP.style.marginLeft = -(
  71. (parseFloat(linkStyle.paddingRight) || 0) +
  72. (parseFloat(linkStyle.marginRight) || 0) +
  73. (parseFloat(linkStyle.borderRightWidth) || 0) +
  74. Math.max(0, a.getBoundingClientRect().right + 32 - innerWidth)
  75. ) + 'px';
  76. setTimeout(() => (POPUP.style.opacity = '1'));
  77. a.parentElement.insertBefore(POPUP, a.nextSibling);
  78. POPUP.addEventListener('click', openOriginal);
  79. }
  80.  
  81. function hidePopup(a) {
  82. if (POPUP.matches(':hover') || lastLink && lastLink.matches(':hover')) {
  83. cancelHover.call(a);
  84. } else if (POPUP.style.opacity === '1') {
  85. POPUP.style.opacity = '0';
  86. cancelHover.call(a);
  87. } else {
  88. lastLink = null;
  89. POPUP.remove();
  90. }
  91. }
  92.  
  93. function openOriginal(e) {
  94. POPUP.href = '';
  95. e.preventDefault();
  96. e.stopPropagation();
  97. e.stopImmediatePropagation();
  98. setTimeout(() => {
  99. lastLink.href = lastLink.hrefUndecloaked;
  100. lastLink.dispatchEvent(new MouseEvent('click', {bubbles: true}));
  101. });
  102. }
  103.  
  104. function decloakLink(event) {
  105. const a = getClosestLink(event);
  106. if (!a || a === POPUP)
  107. return;
  108. if (a.hrefUndecloaked)
  109. return a;
  110.  
  111. if (/\bthis\.href\s*=[^=]/.test(a.getAttribute('onmousedown')))
  112. a.onmousedown = null;
  113. if (/\bthis\.href\s*=[^=]/.test(a.getAttribute('onclick')))
  114. a.onclick = null;
  115.  
  116. const href = a.href.baseVal || a.href;
  117. const m = href.match(/[=?/]((ftps?|https?)((:|%3[Aa])\/\/[^+&]+|%3[Aa]%2[Ff]%2[Ff][^+&/]+))/);
  118. if (!m ||
  119. a.hostname === 'disqus.com' && a.pathname.startsWith('/embed/comments/')) {
  120. return;
  121. }
  122.  
  123. let realUrl = decodeURIComponent(m[1]);
  124. if (a.hostname === 'disq.us' &&
  125. realUrl.lastIndexOf(':') !== realUrl.indexOf(':')) {
  126. realUrl = realUrl.substr(0, realUrl.lastIndexOf(':'));
  127. }
  128.  
  129. if (new URL(realUrl).hostname === a.hostname ||
  130. href.match(/[?&=/]\w*([Ss]ign|[Ll]og)[io]n/)) {
  131. console.debug('Decloak skipped: assumed a login redirection.');
  132. return;
  133. }
  134.  
  135. a.hrefUndecloaked = href;
  136. a.setAttribute('href', realUrl);
  137. a.rel = 'external noreferrer nofollow noopener';
  138. return a;
  139. }
  140.  
  141. function getClosestLink(event) {
  142. const a = event.composedPath
  143. ? event.composedPath().find(el => el.tagName === 'A')
  144. : event.target.closest('a');
  145. return a && /^(f|ht)tps?:/.test(a.getAttribute('href')) && a;
  146. }