Decloak links and open directly

Open redirected/cloaked links directly

目前为 2018-04-06 提交的版本,查看 最新版本

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