Decode base64-encoded links in 4chan posts and various archives
// ==UserScript==
// @name Base64 Link Decoder for 4chan and Archives
// @match *://boards.4channel.org/*
// @namespace TOTK
// @version 963.369
// @match *://boards.4chan.org/*
// @match *://archived.moe/*
// @license MIT
// @match *://archiveofsins.com/*
// @grant none
// @description Decode base64-encoded links in 4chan posts and various archives
// ==/UserScript==
(function() {
'use strict';
// Magischer Base64-Finder!
const base64Regex = /^[A-Za-z0-9+/]{20,}={0,2}$/;
// Ist das eine URL? Schau mal!
const isURL = (str) => {
try {
return /^https?:\/\//i.test(str);
} catch (e) {
return false;
}
};
// Dekodieren und Links machen! Juhu!
function decodeAndReplace(element) {
let content = element.innerHTML;
const lines = content.split(/<br\s*\/?>/i);
for (let i = 0; i < lines.length; i++) {
lines[i] = lines[i].replace(/<wbr>/g, '');
if (base64Regex.test(lines[i])) {
try {
const decoded = atob(lines[i]);
if (decoded.includes('http')) {
const urls = decoded.split(/[\n\r\s]+/).filter(url => isURL(url));
if (urls.length > 0) {
lines[i] = urls.map(url => `<a href="${url}" target="_blank">${url}</a>`).join('<br>');
} else if (isURL(decoded)) {
lines[i] = `<a href="${decoded}" target="_blank">${decoded}</a>`;
}
} else if (isURL(decoded)) {
lines[i] = `<a href="${decoded}" target="_blank">${decoded}</a>`;
}
} catch (e) {}
} else {
const parts = lines[i].split(/\s+/);
for (let j = 0; j < parts.length; j++) {
if (base64Regex.test(parts[j])) {
try {
const decoded = atob(parts[j]);
if (decoded.includes('http')) {
const urls = decoded.split(/[\n\r\s]+/).filter(url => isURL(url));
if (urls.length > 0) {
parts[j] = urls.map(url => `<a href="${url}" target="_blank">${url}</a>`).join('<br>');
} else if (isURL(decoded)) {
parts[j] = `<a href="${decoded}" target="_blank">${decoded}</a>`;
}
} else if (isURL(decoded)) {
parts[j] = `<a href="${decoded}" target="_blank">${decoded}</a>`;
}
} catch (e) {}
}
}
lines[i] = parts.join(' ');
}
}
element.innerHTML = lines.join('<br>');
}
// Neue Posts checken! Hurra!
function processNewPosts() {
const posts = document.querySelectorAll('.postMessage');
posts.forEach(decodeAndReplace);
const archivePosts = document.querySelectorAll('.text');
archivePosts.forEach(decodeAndReplace);
}
// Los geht's! Script starten!
function initScript() {
console.log("Base64 Decoder gestartet! Yeah!");
processNewPosts();
// Wie ein Spion beobachten!
const observer = new MutationObserver((mutations) => {
let shouldProcess = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.classList && (
node.classList.contains('postContainer') ||
node.querySelector('.postMessage')
)) {
shouldProcess = true;
}
if (node.classList && (
node.classList.contains('post') ||
node.querySelector('.text')
)) {
shouldProcess = true;
}
}
});
}
});
if (shouldProcess) {
processNewPosts();
}
});
observer.observe(document.body, { childList: true, subtree: true });
// Alle 10 Sekunden gucken! Tick tack!
setInterval(processNewPosts, 10000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initScript);
} else {
initScript();
}
})();