Decode base64-encoded links in 4chan posts and various archives
// ==UserScript==
// @name Base64 Link Decoder for 4chan and Archives
// @namespace Violentmonkey Scripts
// @match *://boards.4channel.org/*
// @match *://boards.4chan.org/*
// @match *://archived.moe/*
// @match *://archiveofsins.com/*
// @grant none
// @version 1.0
// @description Decode base64-encoded links in 4chan posts and various archives
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const base64Regex = /^[A-Za-z0-9+/]+=*$/;
const isURL = (str) => /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i.test(str);
function decodeAndReplace(element) {
const html = element.innerHTML;
const modifiedHtml = html.replace(/<wbr>/g, ''); // Remove <wbr> tags
element.innerHTML = modifiedHtml;
const textNodes = [];
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
let node;
while (node = walker.nextNode()) {
textNodes.push(node);
}
textNodes.forEach(node => {
const parts = node.textContent.split(/\s+/);
let changed = false;
for (let i = 0; i < parts.length; i++) {
if (base64Regex.test(parts[i])) {
try {
const decoded = atob(parts[i]);
if (isURL(decoded)) {
parts[i] = `<a href="${decoded}" target="_blank">${decoded}</a>`;
changed = true;
}
} catch (e) {
// Not a valid base64 string, skip
}
}
}
if (changed) {
const span = document.createElement('span');
span.innerHTML = parts.join(' ');
node.parentNode.replaceChild(span, node);
}
});
}
function processNewPosts() {
// For 4chan
const posts = document.querySelectorAll('.postMessage');
posts.forEach(decodeAndReplace);
// For Archives (ArchiveOfSins, Archived.Moe, etc.)
const archivePosts = document.querySelectorAll('.text');
archivePosts.forEach(decodeAndReplace);
}
function initScript() {
// Initial processing
processNewPosts();
// Watch for new posts
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
// For 4chan
if (node.classList.contains('postContainer')) {
const postMessage = node.querySelector('.postMessage');
if (postMessage) decodeAndReplace(postMessage);
}
// For Archives
if (node.classList.contains('post')) {
const textElement = node.querySelector('.text');
if (textElement) decodeAndReplace(textElement);
}
}
});
}
});
});
observer.observe(document.body, { childList: true, subtree: true });
}
// Delay script execution by 5 seconds
setTimeout(initScript, 5000);
})();