Automatic URL Decoder

Декодирует все найденные на странице ссылки, похожие на "%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%BC%D0%B8%D1%80", в удобочитаемое "привет мир"

当前为 2018-10-17 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Automatic URL Decoder
  3. // @namespace https://github.com/T1mL3arn
  4. // @author T1mL3arn
  5. // @description:ru Декодирует все найденные на странице ссылки, похожие на "%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%BC%D0%B8%D1%80", в удобочитаемое "привет мир"
  6. // @description:en It decodes all percent-encoded links on current page.
  7. // @match *://*/*
  8. // @exclude-match https://github.com/t1ml3arn-userscript-js/Automatic-URL-Decoder
  9. // @exclude https://github.com/t1ml3arn-userscript-js/Automatic-URL-Decoder
  10. // @version 2.1
  11. // @run-at document-end
  12. // @license GPLv3
  13. // @supportURL https://github.com/T1mL3arn/Automatic-URL-Decoder/issues
  14. // @homepageURL https://github.com/T1mL3arn/Automatic-URL-Decoder
  15. // @description Декодирует все найденные на странице ссылки, похожие на "%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%BC%D0%B8%D1%80", в удобочитаемое "привет мир"
  16. // ==/UserScript==
  17.  
  18. (() => {
  19. const DECODED_ELT_CLASS = 'auto-url-decoder-s739';
  20. const underlineCss = `
  21. .${DECODED_ELT_CLASS} {
  22. border-bottom: 2px solid currentColor !important;
  23. margin-bottom: -2px !important;
  24. }`;
  25. const CSS_greenBackground = getDefaultBackgroundCSS('#deffc3');
  26. const CSS_redBackground = getDefaultBackgroundCSS('#fcd7d7');
  27. const CSS_blueBackground = getDefaultBackgroundCSS('#d7ebfc');
  28.  
  29. function addStyle(css) {
  30. const id = 'auto-url-decoder-style-elt';
  31. const style = document.getElementById(id) || document.head.appendChild(document.createElement('style'));
  32. style.id = id;
  33. style.textContent = css;
  34. }
  35.  
  36. function getDefaultBackgroundCSS(color) {
  37. return `
  38. .${DECODED_ELT_CLASS} {
  39. background-color: ${color} !important;
  40. padding: 2px 2px !important;
  41. margin: 0 -2px !important;
  42. }`;
  43. }
  44.  
  45. function fixLinks(node) {
  46. if (node.nodeType === 3) {
  47. let content = node.textContent;
  48. if (content != '') {
  49. if (linkEregLocal.test(content) && percentEncodingEreg.test(content)) {
  50. if (decodedNodeCSS != null) replaceAndStyleLink(node);
  51. else {
  52. try {
  53. let decoded = content.replace(linkEreg, decodeURIComponent);
  54. if (decoded.length != content.length) node.textContent = decoded;
  55. } catch (e) {
  56. // URI mailformed, hust skip it
  57. }
  58. }
  59. }
  60. }
  61. } else if (node.nodeType === 1 && node.childNodes.length > 0 && isElementAllowed(node)) {
  62. node.childNodes.forEach(fixLinks);
  63. }
  64. }
  65. function isElementAllowed(elt) {
  66. return blockedTagsList.indexOf(elt.tagName) == -1 && !elt.matches(blockedClassesSelector);
  67. }
  68.  
  69. function replaceAndStyleLink(node) {
  70. let match;
  71. let sibling = node;
  72. let content = node.textContent;
  73. while ((match = linkEreg.exec(content)) != null) {
  74. let fullMatch = match[0];
  75. let decoded;
  76.  
  77. try {
  78. decoded = decodeURIComponent(fullMatch);
  79. } catch (e) {
  80. content = content.substring(linkEreg.lastIndex);
  81. linkEreg.lastIndex = 0;
  82. continue;
  83. }
  84.  
  85. if (decoded.length != fullMatch.length) {
  86. let span = document.createElement('span');
  87. span.classList.add(DECODED_ELT_CLASS);
  88.  
  89. if (storeOriginalURL) span.dataset.urlDecOriginalUrl = fullMatch;
  90. let range = document.createRange();
  91. range.setStart(sibling, linkEreg.lastIndex - match[0].length);
  92. range.setEnd(sibling, linkEreg.lastIndex);
  93. range.surroundContents(span);
  94. content = content.substring(linkEreg.lastIndex);
  95. span.textContent = decoded;
  96. linkEreg.lastIndex = 0;
  97. counter++;
  98.  
  99. sibling = getNextTextSibling(span);
  100. if (sibling == null) break;
  101. }
  102. }
  103. }
  104. function getNextTextSibling(node) {
  105. let next = node.nextSibling;
  106. while (next != null) {
  107. if (next.nodeType == 3) return next;
  108. else next = node.nextSibling;
  109. }
  110. return null;
  111. }
  112.  
  113. let linkEreg = /(?:[a-z][a-z0-9-+.]+:\/\/|www\.).+?(?=\s|$)/gi;
  114. let linkEregLocal = /(?:[a-z][a-z0-9-+.]+:\/\/|www\.).+?(?=\s|$)/i;
  115. let percentEncodingEreg = /%[a-f0-9]{2}/i;
  116. let obsOptions = { childList: true, subtree: true };
  117. let blockedTagsList = 'NOSCRIPT OPTION SCRIPT STYLE TEXTAREA SVG CANVAS BUTTON SELECT TEMPLATE METER PROGRESS MATH TIME HEAD CODE PRE'.split(' ');
  118. ///NOTE Use 'foo' (or any other dummy class) in this selector
  119. // if you need to make this variable "empty"
  120. // It allows to avoid SyntaxError: '' is not a valid selector
  121. let blockedClassesSelector = `${DECODED_ELT_CLASS}`.split(' ').map(class_ => `.${class_}`).join(', ');
  122. let counter = 0;
  123. let storeOriginalURL = false;
  124.  
  125. // set one of style from above to enable
  126. // custom style for fixed links
  127. let decodedNodeCSS = CSS_greenBackground;
  128. let obs = new MutationObserver((changes, obs) => {
  129. counter = 0;
  130. obs.disconnect();
  131. changes.forEach((change) => change.addedNodes.forEach((node) => fixLinks(node)) );
  132. obs.observe(document.body, obsOptions);
  133. //console.log('[ URL Decoder ] Decoded: ', counter);
  134. });
  135. if (decodedNodeCSS != null) addStyle(decodedNodeCSS);
  136. counter = 0;
  137. fixLinks(document.body);
  138. //console.log('[ URL Decoder ] Decoded: ', counter);
  139. obs.observe(document.body, obsOptions);
  140. })();