Automatic URL Decoder

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

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