Decode base64-encoded links in some pastebins and make URLs clickable
// ==UserScript==
// @name FMHY Base64 Auto Decoder
// @namespace http://tampermonkey.net/
// @version 3.1
// @license MIT
// @description Decode base64-encoded links in some pastebins and make URLs clickable
// @author Mumukshu D.C
// @match *://rentry.co/*
// @match *://rentry.org/*
// @match *://pastes.fmhy.net/*
// @match *://bin.disroot.org/?*#*
// @match *://privatebin.net/?*#*
// @match *://textbin.xyz/?*#*
// @match *://bin.idrix.fr/?*#*
// @match *://privatebin.rinuploads.org/?*#*
// @match *://pastebin.com/*
// @grant none
// @icon https://t1.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://fmhy.net&size=64
// ==/UserScript==
(function() {
'use strict';
const Utils = {
base64Regex: /^[A-Za-z0-9+/]+={0,2}$/,
urlRegex: /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i,
isBase64(str) {
return this.base64Regex.test(str);
},
decode(str) {
try {
return atob(str);
} catch (e) {
return null;
}
},
isUrl(str) {
return this.urlRegex.test(str);
},
isSafeUrl(str) {
if (typeof str !== 'string') return false;
// Strip leading/trailing control characters and spaces
const sanitized = str.replace(/^[\s\u0000-\u001F]+/g, '').trim().toLowerCase();
if (sanitized.startsWith('javascript:') || sanitized.startsWith('data:') || sanitized.startsWith('vbscript:')) {
return false;
}
return true;
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
},
linkify(text, style = '', target = '_blank') {
const escapedText = this.escapeHTML(text);
const urlPattern = /(https?:\/\/[^\s]+)/g;
return escapedText.replace(urlPattern, (url) => {
const decodedUrl = url.replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
if (!this.isSafeUrl(decodedUrl)) {
return url; // Don't linkify unsafe schemes
}
return `<a href="${url}" target="${target}" style="${style}">${url}</a>`;
});
}
};
const HANDLERS = [
{
name: 'Pastebin',
match: /^https:\/\/pastebin\.com\/.*/,
selector: '.de1',
process: (el) => {
let text = el.textContent.trim();
if (text.startsWith('aHR0')) {
const decoded = Utils.decode(text);
if (decoded && Utils.isSafeUrl(decoded)) {
const originalColor = window.getComputedStyle(el).color;
el.innerHTML = Utils.linkify(decoded, `color: ${originalColor};`).replace(/\n/g, '<br>');
}
}
}
},
{
name: 'Rentry/FMHY',
match: /rentry\.(co|org)|pastes\.fmhy\.net/,
selector: (url) => /^https:\/\/rentry\.(co|org)\/fmhybase64/i.test(url) ? 'code' : 'code, p',
process: (el) => {
const content = el.textContent.trim();
if (Utils.isBase64(content)) {
const decoded = Utils.decode(content)?.trim();
if (decoded && (Utils.isUrl(decoded) || decoded.includes('http'))) {
// Prevent javascript: and other dangerous schemes
if (!Utils.isSafeUrl(decoded)) return;
if (!decoded.includes('\n')) {
const escaped = Utils.escapeHTML(decoded);
el.innerHTML = `<a href="${escaped}" target="_self">${escaped}</a>`;
} else {
el.innerHTML = decoded.split('\n')
.map(line => {
const trimmed = line.trim();
if (Utils.isUrl(trimmed) && Utils.isSafeUrl(trimmed)) {
const escaped = Utils.escapeHTML(trimmed);
return `<a href="${escaped}">${escaped}</a>`;
}
return Utils.escapeHTML(line);
})
.join('<br>');
}
}
}
}
},
{
name: 'PrivateBin',
match: /bin\.disroot\.org|privatebin\.net|textbin\.xyz|bin\.idrix\.fr|privatebin\.rinuploads\.org/,
selector: '#prettyprint',
process: (el) => {
let content = el.innerHTML.trim();
const lines = content.split('\n');
let modified = false;
const processedLines = lines.map(line => {
let target = line;
if (line.startsWith('`') && line.endsWith('`')) {
target = line.slice(1, -1);
}
if (Utils.isBase64(target)) {
const decoded = Utils.decode(target)?.trim();
if (decoded && Utils.isUrl(decoded) && Utils.isSafeUrl(decoded)) {
modified = true;
const escaped = Utils.escapeHTML(decoded);
return `<a href="${escaped}">${escaped}</a>`;
}
}
return line;
});
if (modified) {
el.innerHTML = processedLines.join('\n');
}
}
}
];
const Engine = {
activeHandler: null,
init() {
const url = window.location.href;
this.activeHandler = HANDLERS.find(h =>
typeof h.match === 'function' ? h.match(url) : h.match.test(url)
);
if (!this.activeHandler) return;
this.observe();
this.processAll();
},
processAll() {
const selector = typeof this.activeHandler.selector === 'function'
? this.activeHandler.selector(window.location.href)
: this.activeHandler.selector;
document.querySelectorAll(selector).forEach(el => this.processElement(el));
},
processElement(el) {
if (el.dataset.fmhyDecoded) return;
this.activeHandler.process(el);
el.dataset.fmhyDecoded = 'true';
},
observe() {
const observer = new MutationObserver((mutations) => {
const selector = typeof this.activeHandler.selector === 'function'
? this.activeHandler.selector(window.location.href)
: this.activeHandler.selector;
const nodesToProcess = [];
for (let i = 0; i < mutations.length; i++) {
const addedNodes = mutations[i].addedNodes;
for (let j = 0; j < addedNodes.length; j++) {
const node = addedNodes[j];
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.matches(selector)) {
nodesToProcess.push(node);
}
const children = node.querySelectorAll(selector);
for (let k = 0; k < children.length; k++) {
nodesToProcess.push(children[k]);
}
}
}
}
for (let i = 0; i < nodesToProcess.length; i++) {
this.processElement(nodesToProcess[i]);
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
};
Engine.init();
})();