Decloak links and open directly

Open redirected/cloaked links directly

当前为 2019-03-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Decloak links and open directly
  3. // @description Open redirected/cloaked links directly
  4. // @version 2.1.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. var POPUP = document.createElement('a');
  16. POPUP.id = GM_info.script.name;
  17. POPUP.title = 'Original link';
  18. POPUP.style.cssText = 'all: unset;' +
  19. 'width: 18px;' +
  20. 'height: 18px;' +
  21. 'background: url("' + GM_getResourceURL('icon') + '") center no-repeat;' +
  22. 'background-size: 16px;' +
  23. 'background-color: white;' +
  24. 'opacity: 0;' +
  25. 'transition: opacity .5s;' +
  26. 'border: 1px solid #888;' +
  27. 'border-radius: 11px;' +
  28. 'z-index: 2147483647;' +
  29. 'margin-left: 0;' +
  30. 'position: absolute;'
  31. .replace(/;/g, '!important;');
  32.  
  33. var allLinks = document.getElementsByTagName('a');
  34.  
  35. window.addEventListener('mousedown', decloakOnClick, true);
  36. window.addEventListener('keydown', function(e) { if (e.keyCode == 13) decloakOnClick(e) }, true);
  37.  
  38. window.addEventListener('load', function _() {
  39. window.removeEventListener('load', _);
  40. process(allLinks);
  41. new MutationObserver(function(mutations) {
  42. if (allLinks[0])
  43. Promise.resolve().then(() => extractLinks(mutations));
  44. }).observe(document.body || document, {childList: true, subtree: true});
  45. });
  46.  
  47. function extractLinks(mutations) {
  48. var extracted = [];
  49. for (var m = 0, ml = mutations.length; m < ml; m++) {
  50. for (var n = 0, added = mutations[m].addedNodes, nl = added.length; n < nl; n++) {
  51. var node = added[n];
  52. if (node.localName == 'a')
  53. extracted.push(node);
  54. else if ('children' in node && node.children[0]) {
  55. var childLinks = node.getElementsByTagName('a');
  56. if (childLinks[0])
  57. [].push.apply(extracted, childLinks);
  58. }
  59. }
  60. }
  61. process(extracted);
  62. }
  63.  
  64. function process(links) {
  65. for (var i = 0, len = links.length; i < len; i++) {
  66. var a = links[i];
  67. if (a.href.match(/^(http|ftp)/) && decloak(a))
  68. a.addEventListener('mouseover', onHover, true);
  69. }
  70. }
  71.  
  72. function onHover(e) {
  73. if (onHover.element)
  74. onHover.element.removeEventListener('mouseout', cancelHover);
  75. clearTimeout(onHover.timeout);
  76. onHover.timeout = setTimeout(showPopup, 500, this);
  77. onHover.element = this;
  78. this.addEventListener('mouseout', cancelHover);
  79. }
  80.  
  81. function cancelHover(e) {
  82. this.removeEventListener('mouseout', cancelHover);
  83. clearTimeout(cancelHover.timeout);
  84. cancelHover.timeout = setTimeout(hidePopup, 500, this);
  85. }
  86.  
  87. function showPopup(a) {
  88. if (!a.parentElement || !a.matches(':hover'))
  89. return;
  90. var linkStyle = getComputedStyle(a);
  91. POPUP.href = a.hrefUndecloaked;
  92. POPUP.style.opacity = '0';
  93. POPUP.style.marginLeft = -(
  94. (parseFloat(linkStyle.paddingRight) || 0) +
  95. (parseFloat(linkStyle.marginRight) || 0) +
  96. (parseFloat(linkStyle.borderRightWidth) || 0) +
  97. Math.max(0, a.getBoundingClientRect().right + 32 - innerWidth)
  98. ) + 'px';
  99. setTimeout(function() { POPUP.style.opacity = '1' }, 0);
  100. a.parentElement.insertBefore(POPUP, a.nextSibling);
  101. POPUP.addEventListener('click', openOriginal);
  102. }
  103.  
  104. function hidePopup(a) {
  105. if (POPUP.matches(':hover') || onHover.element && onHover.element.matches(':hover')) {
  106. cancelHover.call(a);
  107. } else if (POPUP.style.opacity == '1') {
  108. POPUP.style.opacity = '0';
  109. cancelHover.call(a);
  110. } else {
  111. onHover.element = null;
  112. POPUP.remove();
  113. }
  114. }
  115.  
  116. function openOriginal(e) {
  117. POPUP.href = '';
  118. e.preventDefault();
  119. e.stopPropagation();
  120. e.stopImmediatePropagation();
  121. setTimeout(function() {
  122. onHover.element.href = onHover.element.hrefUndecloaked;
  123. onHover.element.dispatchEvent(new MouseEvent('click', {bubbles: true}));
  124. });
  125. }
  126.  
  127. function decloak(a) {
  128. if (a == POPUP)
  129. return;
  130.  
  131. if (/\bthis\.href\s*=[^=]/.test(a.getAttribute('onmousedown')))
  132. a.onmousedown = null;
  133. if (/\bthis\.href\s*=[^=]/.test(a.getAttribute('onclick')))
  134. a.onclick = null;
  135.  
  136. var m = a.href.match(/[=?]((?:ftp|https?|s?)(?::\/\/[^+&]+|%3[Aa]%2[Ff]%2[Ff][^+&\/]+))/);
  137. if (!m)
  138. return;
  139.  
  140. if (a.hostname == 'disqus.com' && a.pathname.startsWith('/embed/comments/'))
  141. return;
  142.  
  143. var realUrl = decodeURIComponent(m[1]);
  144. if (/^s?:/.test(realUrl))
  145. realUrl = 'http' + realUrl;
  146.  
  147. if (a.hostname == 'disq.us' && realUrl.lastIndexOf(':') != realUrl.indexOf(':'))
  148. realUrl = realUrl.substr(0, realUrl.lastIndexOf(':'));
  149.  
  150. if (new URL(realUrl).hostname == a.hostname || a.href.match(/[?&=\/]\w*([Ss]ign|[Ll]og)[io]n/)) {
  151. console.debug('Decloak skipped: assumed a login redirection.');
  152. return;
  153. }
  154.  
  155. a.hrefUndecloaked = a.href;
  156. a.href = realUrl;
  157. return true;
  158. }
  159.  
  160. function decloakOnClick(e) {
  161. var a = e.target.closest && e.target.closest('a');
  162. if (!a || !a.hrefUndecloaked || !a.href.match(/^(http|ftp)/))
  163. return;
  164. decloak(a);
  165. }