🏠 Home 

Automatic URL Decoder

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

// ==UserScript==
// @name Automatic URL Decoder
// @namespace https://github.com/T1mL3arn
// @author T1mL3arn
// @description:ru Декодирует все найденные на странице ссылки, похожие на "%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%BC%D0%B8%D1%80", в удобочитаемое "привет мир"
// @description:en It decodes all percent-encoded links on current page.
// @match *://*/*
// @exclude-match https://github.com/t1ml3arn-userscript-js/Automatic-URL-Decoder
// @exclude https://github.com/t1ml3arn-userscript-js/Automatic-URL-Decoder
// @exclude-match https://greasyfork.org/en/scripts/40305-automatic-url-decoder
// @exclude https://greasyfork.org/en/scripts/40305-automatic-url-decoder
// @version 2.1.2
// @run-at document-end
// @license GPLv3
// @supportURL https://github.com/T1mL3arn/Automatic-URL-Decoder/issues
// @homepageURL https://github.com/T1mL3arn/Automatic-URL-Decoder
// @description Декодирует все найденные на странице ссылки, похожие на "%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%BC%D0%B8%D1%80", в удобочитаемое "привет мир"
// ==/UserScript==
(() => {
const DECODED_ELT_CLASS = 'auto-url-decoder-s739';
// Predefined css styles for decoded links //
// To change css find `decodedNodeCSS` variable
// and set one of the style below to it.
// Set `null` to `decodedNodeCSS` to disable styling.
const underlineCss = `
.${DECODED_ELT_CLASS} {
border-bottom: 2px solid currentColor !important;
margin-bottom: -2px !important;
}`;
const CSS_greenBackground = getDefaultBackgroundCSS('#deffc3');
const CSS_redBackground = getDefaultBackgroundCSS('#fcd7d7');
const CSS_blueBackground = getDefaultBackgroundCSS('#d7ebfc');
// ----------- //
function addStyle(css) {
const id = 'auto-url-decoder-style-elt';
const style = document.getElementById(id) || document.head.appendChild(document.createElement('style'));
style.id = id;
style.textContent = css;
}
function getDefaultBackgroundCSS(color) {
return `
.${DECODED_ELT_CLASS} {
background-color: ${color} !important;
padding: 2px 2px !important;
margin: 0 -2px !important;
}`;
}
function fixLinks(node) {
if (node.nodeType === 3) {
let content = node.textContent;
if (content != '') {
if (linkEregLocal.test(content) && percentEncodingEreg.test(content)) {
if (decodedNodeCSS != null) replaceAndStyleLink(node);
else {
try {
let decoded = content.replace(linkEreg, decodeURIComponent);
if (decoded.length != content.length) node.textContent = decoded;
} catch (e) {
// URI mailformed, hust skip it
}
}
}
}
} else if (node.nodeType === 1 && node.childNodes.length > 0 && isElementAllowed(node)) {
node.childNodes.forEach(fixLinks);
}
}
function isElementAllowed(elt) {
return blockedTagsList.indexOf(elt.tagName) == -1 && !elt.matches(blockedClassesSelector);
}
function replaceAndStyleLink(node) {
let match;
let sibling = node;
let content = node.textContent;
while ((match = linkEreg.exec(content)) != null) {
let fullMatch = match[0];
let decoded;
try {
decoded = decodeURIComponent(fullMatch);
} catch (e) {
content = content.substring(linkEreg.lastIndex);
linkEreg.lastIndex = 0;
continue;
}
if (decoded.length != fullMatch.length) {
let span = document.createElement('span');
span.classList.add(DECODED_ELT_CLASS);
if (storeOriginalURL) span.dataset.urlDecOriginalUrl = fullMatch;
let range = document.createRange();
range.setStart(sibling, linkEreg.lastIndex - match[0].length);
range.setEnd(sibling, linkEreg.lastIndex);
range.surroundContents(span);
content = content.substring(linkEreg.lastIndex);
span.textContent = decoded;
linkEreg.lastIndex = 0;
counter++;
sibling = getNextTextSibling(span);
if (sibling == null)  break;
}
}
}
function getNextTextSibling(node) {
let next = node.nextSibling;
while (next != null) {
if (next.nodeType == 3) return next;
else                    next = node.nextSibling;
}
return null;
}
let linkEreg = /(?:[a-z][a-z0-9-+.]+:\/\/|www\.).+?(?=\s|$)/gi;
let linkEregLocal = /(?:[a-z][a-z0-9-+.]+:\/\/|www\.).+?(?=\s|$)/i;
let percentEncodingEreg = /%[a-f0-9]{2}/i;
let obsOptions = { childList: true, subtree: true };
let blockedTagsList = 'NOSCRIPT OPTION SCRIPT STYLE TEXTAREA SVG CANVAS BUTTON SELECT TEMPLATE METER PROGRESS MATH TIME HEAD CODE PRE'.split(' ');
///NOTE Use 'foo' (or any other dummy class) in this selector
// if you need to make this variable "empty"
// It allows to avoid  SyntaxError: '' is not a valid selector
let blockedClassesSelector = `${DECODED_ELT_CLASS}`.split(' ').map(class_ => `.${class_}`).join(', ');
let counter = 0;
let storeOriginalURL = false;
let decodedNodeCSS = CSS_greenBackground;
let obs = new MutationObserver((changes, obs) => {
counter = 0;
obs.disconnect();
changes.forEach((change) => change.addedNodes.forEach((node) => fixLinks(node)) );
obs.observe(document.body, obsOptions);
//console.log('[ URL  Decoder ] Decoded: ', counter);
});
if (decodedNodeCSS != null) addStyle(decodedNodeCSS);
counter = 0;
fixLinks(document.body);
//console.log('[ URL  Decoder ] Decoded: ', counter);
obs.observe(document.body, obsOptions);
})();