Auto-decodes base64 strings in posts and makes them clickable
// ==UserScript==
// @name DrownedMods Base64 Auto Decoder
// @namespace https://example.com/drownedmods-base64
// @version 1.2
// @description Auto-decodes base64 strings in posts and makes them clickable
// @author Adapted from FMHY with help from Grok.ai
// @license MIT
// @match https://forum.drownedmods.org/*
// @grant none
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
const base64FullRegex = /^[A-Za-z0-9+/]{40,}(={0,2})?$/;
function looksLikeUrl(str) {
str = str.trim();
return /^(https?:\/\/|magnet:\?|ftp:\/\/)/i.test(str) ||
/mega\.nz|mediafire\.com|drive\.google\.com|gofile\.io|pixeldrain\.com/i.test(str) ||
str.endsWith('.torrent') || str.includes('?xt=urn:btih:');
}
function processTextNode(node) {
if (node.nodeType !== Node.TEXT_NODE) return;
const text = node.textContent.trim();
if (text.length < 40 || !base64FullRegex.test(text)) return;
let decoded;
try {
decoded = atob(text).trim();
} catch (e) {
return;
}
if (!looksLikeUrl(decoded) && !decoded.includes('http')) return;
const fragment = document.createDocumentFragment();
if (!decoded.includes('\n')) {
const a = document.createElement('a');
a.href = decoded;
a.textContent = decoded;
a.target = '_blank';
a.rel = 'noopener noreferrer';
a.style.color = '#00aaff';
a.style.fontWeight = 'bold';
fragment.appendChild(document.createTextNode('→ '));
fragment.appendChild(a);
} else {
decoded.split('\n').forEach((line, idx, arr) => {
const trimmed = line.trim();
if (!trimmed) {
fragment.appendChild(document.createElement('br'));
return;
}
if (looksLikeUrl(trimmed)) {
const a = document.createElement('a');
a.href = trimmed;
a.textContent = trimmed;
a.target = '_blank';
a.rel = 'noopener noreferrer';
fragment.appendChild(a);
} else {
fragment.appendChild(document.createTextNode(trimmed));
}
if (idx < arr.length - 1) fragment.appendChild(document.createElement('br'));
});
}
node.parentNode.replaceChild(fragment, node);
}
function walk(node) {
if (!node) return;
if (node.nodeType === Node.ELEMENT_NODE &&
(node.tagName === 'PRE' || node.tagName === 'CODE' ||
node.classList.contains('message-body') ||
node.classList.contains('post-body') ||
node.classList.contains('message-content') ||
node.classList.contains('content') ||
node.classList.contains('post-message') ||
node.tagName === 'BLOCKQUOTE' ||
node.tagName === 'DIV' || node.tagName === 'P')) {
Array.from(node.childNodes).forEach(walk);
}
if (node.nodeType === Node.TEXT_NODE) {
processTextNode(node);
}
}
walk(document.body);
const observer = new MutationObserver(muts => {
muts.forEach(mut => {
mut.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) walk(node);
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
console.log('[DrownedMods Base64 Decoder] Running – looking for encoded links');
})();