// ==UserScript==
// @name Eliminate $$anonymous$$
// @version 1.0.4
// @description Replace $$anonymous$$ on Unity Answers!
// @license MIT
// @author murphyne
// @namespace https://github.com/murphyne
// @match https://answers.unity.com/*
// @icon https://www.google.com/s2/favicons?domain=answers.unity.com
// @updateUrl https://github.com/murphyne/unity-answers-anonymous/releases/latest/download/no-anonymous.meta.js
// @downloadUrl https://github.com/murphyne/unity-answers-anonymous/releases/latest/download/no-anonymous.user.js
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
function eliminateAnonymous () {
const cssStyle = `
span.anonymous {
-moz-text-decoration-line: underline;
-moz-text-decoration-style: dotted;
-moz-text-decoration-color: brown;
-webkit-text-decoration-line: underline;
-webkit-text-decoration-style: dotted;
-webkit-text-decoration-color: brown;
text-decoration-line: underline;
text-decoration-style: dotted;
text-decoration-color: brown;
text-decoration-thickness: 1px;
text-decoration-skip-ink: none;
}
`;
function Config(key, defaultValue) {
this.key = key;
this.defaultValue = defaultValue;
Object.defineProperty(this, "value", {
get() {
return GM_getValue(this.key, defaultValue);
},
set(value) {
GM_setValue(this.key, value);
},
});
}
let isHighlightEnabled = new Config("isHighlightEnabled", true);
let style = {
styleElement: null,
set enabled(value) {
if (value) { this.styleElement = GM_addStyle(cssStyle); }
else { this.styleElement?.remove(); }
}
};
GM_registerMenuCommand('Toggle the highlight', function toggleHighlight () {
isHighlightEnabled.value = !isHighlightEnabled.value;
style.enabled = isHighlightEnabled.value;
});
style.enabled = isHighlightEnabled.value;
var replacements = [
[ /(\$\$anonymous\$\$onoBehaviour)/g, "MonoBehaviour" ],
[ /(Get\$\$anonymous\$\$eyDown)/g, "GetKeyDown" ],
[ /(any\$\$anonymous\$\$eyDown)/g, "anyKeyDown" ],
[ /(ANI\$\$anonymous\$\$ATION)/g, "ANIMATION" ],
[ /(\$\$anonymous\$\$aterial)/g, "Material" ],
[ /(\$\$anonymous\$\$eyCode)/g, "KeyCode" ],
[ /(WOR\$\$anonymous\$\$ING)/g, "WORKING" ],
[ /(Wor\$\$anonymous\$\$ing)/g, "Working" ],
[ /(wor\$\$anonymous\$\$ing)/g, "working" ],
[ /(EXA\$\$anonymous\$\$PLE)/g, "EXAMPLE" ],
[ /(\$\$anonymous\$\$essage)/g, "Message" ],
[ /(\$\$anonymous\$\$anager)/g, "Manager" ],
[ /(THAN\$\$anonymous\$\$S)/g, "THANKS" ],
[ /(INS\$\$anonymous\$\$D)/g, "INSTEAD" ],
[ /(Ins\$\$anonymous\$\$d)/g, "Instead" ],
[ /(ins\$\$anonymous\$\$d)/g, "instead" ],
[ /(THAN\$\$anonymous\$\$)/g, "THANK" ],
[ /(\$\$anonymous\$\$evin)/g, "Kevin" ],
[ /(\$\$anonymous\$\$inda)/g, "Kinda" ],
[ /(is\$\$anonymous\$\$ey)/g, "isKey" ],
[ /(\$\$anonymous\$\$ouse)/g, "Mouse" ],
[ /(\$\$anonymous\$\$athf)/g, "Mathf" ],
[ /(I\$\$anonymous\$\$GUI)/g, "IMGUI" ],
[ /(HO\$\$anonymous\$\$E)/g, "HOME" ],
[ /(O\$\$anonymous\$\$FG)/g, "OMFG" ],
[ /(\$\$anonymous\$\$eep)/g, "Keep" ],
[ /(\$\$anonymous\$\$ath)/g, "Math" ],
[ /(\$\$anonymous\$\$esh)/g, "Mesh" ],
[ /(\$\$anonymous\$\$ove)/g, "Move" ],
[ /(\$\$anonymous\$\$ode)/g, "Mode" ],
[ /(\$\$anonymous\$\$ost)/g, "Most" ],
[ /(\$\$anonymous\$\$ch)/g, "teach" ],
[ /(\$\$anonymous\$\$in)/g, "Min" ],
[ /(\$\$anonymous\$\$ax)/g, "Max" ],
[ /(\$\$anonymous\$\$ey)/g, "Key" ],
[ /(ND\$\$anonymous\$\$)/g, "NDK" ],
[ /(SD\$\$anonymous\$\$)/g, "SDK" ],
[ /(AR\$\$anonymous\$\$)/g, "ARM" ],
[ /(I\$\$anonymous\$\$O)/g, "IMO" ],
[ /(CO\$\$anonymous\$\$)/g, "COM" ],
[ /(AP\$\$anonymous\$\$)/g, "APK" ],
[ /(O\$\$anonymous\$\$G)/g, "OMG" ],
[ /(\$\$anonymous\$\$d)/g, "mind" ],
[ /(\$\$anonymous\$\$m)/g, "team" ],
[ /(I\$\$anonymous\$\$)/g, "IK" ],
[ /(O\$\$anonymous\$\$)/g, "OK" ],
[ /(\$\$anonymous\$\$y)/g, "My" ],
[ /(\$\$anonymous\$\$)/g, "M" ],
];
var nodes = traverseNodeTree(document.body).flat(Infinity);
processNodes(nodes);
const documentObserver = new MutationObserver(function documentCallback (mutations) {
for (let i = 0; i < mutations.length; i++) {
const mutation = mutations[i];
for (let j = 0; j < mutation.addedNodes.length; j++) {
const root = mutation.addedNodes[j];
var nodes = traverseNodeTree(root).flat(Infinity);
processNodes(nodes);
}
}
});
documentObserver.observe(document, {subtree: true, childList: true});
function processNodes (nodes) {
for (let node of nodes) {
if (node.nodeName === "A") {
if (checkAnonymous(node.href)) {
node.href = replaceAnonymous(node.href);
}
}
if (node.nodeType === Node.TEXT_NODE) {
if (node.textContent.trim() !== "") {
if (checkAnonymous(node.textContent)) {
let fragment = new DocumentFragment();
let tokens = tokenize(node.textContent);
for (let token of tokens) {
fragment.appendChild(createNode(token));
}
node.replaceWith(fragment);
}
}
}
}
}
function tokenize (textContent) {
let tokens = [{isAnonymous: false, before: textContent, after: textContent}];
for (let replacement of replacements) {
tokens = tokens.flatMap(function (token) {
if (token.isAnonymous) return [token];
let newStrings = token.after.split(replacement[0]);
return newStrings.map(function (newString) {
return replacement[0].test(newString)
? {isAnonymous: true, before: newString, after: newString.replaceAll(replacement[0], replacement[1])}
: {isAnonymous: false, before: newString, after: newString}
});
});
}
return tokens;
}
function createNode (token) {
if (token.isAnonymous) {
let span = document.createElement("span");
span.classList.add("anonymous");
span.textContent = token.after;
span.title = `${token.before} → ${token.after}`;
return span;
}
else {
return document.createTextNode(token.after);
}
}
function checkAnonymous (str) {
return str.includes("$$anonymous$$");
}
function replaceAnonymous (str) {
for (let replacement of replacements) {
str = str.replaceAll(replacement[0], replacement[1]);
}
return str;
}
function traverseNodeTree (root) {
if (root.childNodes.length > 0) {
var childNodes = Array.from(root.childNodes);
var childNodesDeep = childNodes.map(traverseNodeTree);
return [root].concat(childNodesDeep);
}
else {
return [root];
}
}
}
eliminateAnonymous();