Replace censored profanity in Torn forums with original words (case-preserving)
// ==UserScript==
// @name Unf**k Torn
// @namespace https://torn.com/
// @version 1.1.1
// @description Replace censored profanity in Torn forums with original words (case-preserving)
// @match https://www.torn.com/forums.php*
// @grant none
// @license none
// ==/UserScript==
(function () {
'use strict';
function matchCase(original, replacement) {
if (original === original.toUpperCase()) {
return replacement.toUpperCase();
}
if (original === original.toLowerCase()) {
return replacement.toLowerCase();
}
if (
original[0] === original[0].toUpperCase() &&
original.slice(1) === original.slice(1).toLowerCase()
) {
return replacement.charAt(0).toUpperCase() + replacement.slice(1).toLowerCase();
}
return replacement;
}
// "Not part of a larger word" boundaries
const replacements = [
// ---- FUCK variants: longest first ----
{
regex: /(?<![A-Za-z0-9_])f\*+king(?![A-Za-z0-9_])/gi,
replace: (m) => matchCase(m, 'fucking')
},
{
regex: /(?<![A-Za-z0-9_])f\*+ing(?![A-Za-z0-9_])/gi,
replace: (m) => matchCase(m, 'fucking')
},
{
regex: /(?<![A-Za-z0-9_])f\*cking(?![A-Za-z0-9_])/gi,
replace: (m) => matchCase(m, 'fucking')
},
{
regex: /(?<![A-Za-z0-9_])f\*ck(?![A-Za-z0-9_])/gi,
replace: (m) => matchCase(m, 'fuck')
},
{
regex: /(?<![A-Za-z0-9_])f\*{2,}k(?![A-Za-z0-9_])/gi,
replace: (m) => matchCase(m, 'fuck')
},
{
regex: /(?<![A-Za-z0-9_])f\*{3,}(?![A-Za-z0-9_])/gi,
replace: (m) => matchCase(m, 'fuck')
},
{
regex: /(?<![A-Za-z0-9_])unf\*+k(?![A-Za-z0-9_])/gi,
replace: (m) => matchCase(m, 'unfuck')
},
// ---- CUNT variants ----
{
regex: /(?<![A-Za-z0-9_])c\*nt(?![A-Za-z0-9_])/gi,
replace: (m) => matchCase(m, 'cunt')
},
{
regex: /(?<![A-Za-z0-9_])c\*{2,}t(?![A-Za-z0-9_])/gi,
replace: (m) => matchCase(m, 'cunt')
},
{
regex: /(?<![A-Za-z0-9_])c\*{3,}(?![A-Za-z0-9_])/gi,
replace: (m) => matchCase(m, 'cunt')
}
];
function processTextNode(node) {
const text = node.nodeValue;
let updated = text;
for (const rule of replacements) {
updated = updated.replace(rule.regex, rule.replace);
}
if (updated !== text) {
node.nodeValue = updated;
}
}
function walk(node) {
if (node.nodeType === Node.TEXT_NODE) {
processTextNode(node);
} else if (node.nodeType === Node.ELEMENT_NODE) {
if (['SCRIPT', 'STYLE', 'TEXTAREA'].includes(node.tagName)) return;
for (const child of node.childNodes) {
walk(child);
}
}
}
function run() {
if (document.body) walk(document.body);
}
run();
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
for (const node of m.addedNodes) {
walk(node);
}
}
});
if (document.body) {
observer.observe(document.body, {
childList: true,
subtree: true
});
}
})();