Eliminate $$anonymous$$

Replace $$anonymous$$ on Unity Answers!

  1. // ==UserScript==
  2. // @name Eliminate $$anonymous$$
  3. // @version 1.0.6
  4. // @description Replace $$anonymous$$ on Unity Answers!
  5. // @license MIT
  6. // @author murphyne
  7. // @namespace https://github.com/murphyne
  8. // @match https://answers.unity.com/*
  9. // @icon https://www.google.com/s2/favicons?domain=answers.unity.com
  10. // @updateUrl https://github.com/murphyne/unity-answers-anonymous/releases/latest/download/no-anonymous.meta.js
  11. // @downloadUrl https://github.com/murphyne/unity-answers-anonymous/releases/latest/download/no-anonymous.user.js
  12. // @grant GM_addStyle
  13. // @grant GM_registerMenuCommand
  14. // @grant GM_getValue
  15. // @grant GM_setValue
  16. // ==/UserScript==
  17.  
  18. function eliminateAnonymous () {
  19.  
  20. const cssStyle = `
  21. span.anonymous {
  22. -moz-text-decoration-line: underline;
  23. -moz-text-decoration-style: dotted;
  24. -moz-text-decoration-color: brown;
  25. -webkit-text-decoration-line: underline;
  26. -webkit-text-decoration-style: dotted;
  27. -webkit-text-decoration-color: brown;
  28. text-decoration-line: underline;
  29. text-decoration-style: dotted;
  30. text-decoration-color: brown;
  31. text-decoration-thickness: 1px;
  32. text-decoration-skip-ink: none;
  33. }
  34. `;
  35.  
  36. function Config(key, defaultValue) {
  37. this.key = key;
  38. this.defaultValue = defaultValue;
  39.  
  40. Object.defineProperty(this, "value", {
  41. get() {
  42. return GM_getValue(this.key, defaultValue);
  43. },
  44. set(value) {
  45. GM_setValue(this.key, value);
  46. },
  47. });
  48. }
  49.  
  50. let isHighlightEnabled = new Config("isHighlightEnabled", true);
  51.  
  52. let style = {
  53. styleElement: null,
  54. set enabled(value) {
  55. if (value) { this.styleElement = GM_addStyle(cssStyle); }
  56. else { this.styleElement?.remove(); }
  57. }
  58. };
  59.  
  60. GM_registerMenuCommand('Toggle the highlight', function toggleHighlight () {
  61. isHighlightEnabled.value = !isHighlightEnabled.value;
  62. style.enabled = isHighlightEnabled.value;
  63. });
  64.  
  65. style.enabled = isHighlightEnabled.value;
  66.  
  67. var replacements = [
  68. [ /(\$\$anonymous\$\$)/g, "hi" ],
  69. ];
  70.  
  71. var nodes = traverseNodeTree(document.body).flat(Infinity);
  72. processNodes(nodes);
  73.  
  74. const documentObserver = new MutationObserver(function documentCallback (mutations) {
  75. for (let i = 0; i < mutations.length; i++) {
  76. const mutation = mutations[i];
  77. for (let j = 0; j < mutation.addedNodes.length; j++) {
  78. const root = mutation.addedNodes[j];
  79. var nodes = traverseNodeTree(root).flat(Infinity);
  80. processNodes(nodes);
  81. }
  82. }
  83. });
  84. documentObserver.observe(document, {subtree: true, childList: true});
  85.  
  86. function processNodes (nodes) {
  87. for (let node of nodes) {
  88. if (node.nodeName === "A") {
  89. if (checkAnonymous(node.href)) {
  90. node.href = replaceAnonymous(node.href);
  91. }
  92. }
  93.  
  94. if (node.nodeType === Node.TEXT_NODE) {
  95. if (node.textContent.trim() !== "") {
  96. if (checkAnonymous(node.textContent)) {
  97. let fragment = new DocumentFragment();
  98.  
  99. let tokens = tokenize(node.textContent);
  100. for (let token of tokens) {
  101. fragment.appendChild(createNode(token));
  102. }
  103.  
  104. node.replaceWith(fragment);
  105. }
  106. }
  107. }
  108. }
  109. }
  110.  
  111. function tokenize (textContent) {
  112. let tokens = [{isAnonymous: false, before: textContent, after: textContent}];
  113. for (let replacement of replacements) {
  114. tokens = tokens.flatMap(function (token) {
  115. if (token.isAnonymous) return [token];
  116.  
  117. let newStrings = token.after.split(replacement[0]);
  118. return newStrings.map(function (newString) {
  119. return replacement[0].test(newString)
  120. ? {isAnonymous: true, before: newString, after: newString.replaceAll(replacement[0], replacement[1])}
  121. : {isAnonymous: false, before: newString, after: newString}
  122. });
  123. });
  124. }
  125. return tokens;
  126. }
  127.  
  128. function createNode (token) {
  129. if (token.isAnonymous) {
  130. let span = document.createElement("span");
  131. span.classList.add("anonymous");
  132. span.textContent = token.after;
  133. span.title = `${token.before} ${token.after}`;
  134. return span;
  135. }
  136. else {
  137. return document.createTextNode(token.after);
  138. }
  139. }
  140.  
  141. function checkAnonymous (str) {
  142. return str.includes("$$anonymous$$");
  143. }
  144.  
  145. function replaceAnonymous (str) {
  146. for (let replacement of replacements) {
  147. str = str.replaceAll(replacement[0], replacement[1]);
  148. }
  149. return str;
  150. }
  151.  
  152. function traverseNodeTree (root) {
  153. if (root.childNodes.length > 0) {
  154. var childNodes = Array.from(root.childNodes);
  155. var childNodesDeep = childNodes.map(traverseNodeTree);
  156. return [root].concat(childNodesDeep);
  157. }
  158. else {
  159. return [root];
  160. }
  161. }
  162.  
  163. }
  164.  
  165. eliminateAnonymous();