// ==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 // @downloadURL none // ==/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();