Richup.io Name & Flag Replacer (Lithuanian Cities)

Replaces default city/country names and flags with Lithuanian city names and coats of arms on richup.io game pages.

  1. // ==UserScript==
  2. // @name Richup.io Name & Flag Replacer (Lithuanian Cities)
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.1.3
  5. // @description Replaces default city/country names and flags with Lithuanian city names and coats of arms on richup.io game pages.
  6. // @author Giedrius Rackevicius
  7. // @match https://richup.io/room/*
  8. // @grant none
  9. // @run-at document-idle
  10. // ==/UserScript==
  11.  
  12. (() => {
  13. "use strict";
  14.  
  15. const targetSelector =
  16. '[class*="richup-block-top"], [class*="richup-block-left"], [class*="richup-block-right"], [class*="richup-block-bottom"]';
  17.  
  18. const waitForElements = () => {
  19. const interval = setInterval(() => {
  20. const target = document.querySelector(targetSelector);
  21. if (target) {
  22. clearInterval(interval);
  23. console.log(
  24. "Richup.io Replacer: Target elements found. Initializing script...",
  25. );
  26. initializeScript();
  27. } else {
  28. console.log(
  29. "Richup.io Replacer: Target elements not found. Waiting...",
  30. );
  31. }
  32. }, 500);
  33. };
  34.  
  35. requestAnimationFrame(waitForElements);
  36.  
  37. const initializeScript = () => {
  38. // --- Configuration: Text Replacements (Old Name -> New Name) ---
  39. const textReplacements = {
  40. // Marijampolė
  41. Brazil: "Marijampolė",
  42. Salvador: "Degučiai",
  43. Rio: "Mokolai",
  44. // Utena
  45. Israel: "Utena",
  46. "Tel Aviv": "Vyturiai",
  47. Haifa: "Dauniškis",
  48. Jerusalem: "Aukštakalnis",
  49. // Alytus
  50. Italy: "Alytus",
  51. Venice: "Vidzgiris",
  52. Milan: "Putinai",
  53. Rome: "Dainava",
  54. // Panevėžys
  55. Germany: "Panevėžys",
  56. Frankfurt: "Tulpės",
  57. Munich: "Pilėnai",
  58. Berlin: "Senvagė",
  59. // Šiauliai
  60. China: "Šiauliai",
  61. Shenzhen: "Dainai",
  62. Beijing: "Zokniai",
  63. Shanghai: "Didždvaris",
  64. // Klaipėda
  65. France: "Klaipėda",
  66. Lyon: "Baltija",
  67. Toulouse: "Rumpiškė",
  68. Paris: "Vėtrungė",
  69. // Kaunas
  70. "United Kingdom": "Kaunas",
  71. Liverpool: "Žaliakalnis",
  72. Manchester: "Šilainiai",
  73. London: "Centras",
  74. // Vilnius
  75. USA: "Vilnius",
  76. "San Francisco": "Naujamiestis",
  77. "New York": "Senamiestis",
  78. // Airports
  79. "TLV Airport": "SQQ Airport",
  80. "MUC Airport": "PLQ Airport",
  81. "CDG Airport": "KUN Airport",
  82. "JFK Airport": "VNO Airport",
  83. // Companies
  84. "Electric Company": "Ignitis",
  85. "Water Company": "Utenos Vandenys",
  86. // Prison related phrases
  87. "out of prison": "out of Lukiškės",
  88. "in prison": "in Lukiškės",
  89. "In Prison": "In Lukiškės",
  90. "got into prison": "got into Lukiškės",
  91. };
  92.  
  93. // Helper to normalize SVG strings for consistent matching
  94. const normalizeSvgString = (svgString) => {
  95. if (!svgString) return "";
  96. return svgString.replace(/>\s+</g, "><").replace(/\s\s+/g, " ").trim();
  97. };
  98.  
  99. // --- Configuration: SVG Replacements (Normalized Original SVG HTML -> Replacement Image URL) ---
  100. const svgReplacements = {
  101. // Brazil / Marijampolė
  102. [normalizeSvgString(
  103. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xml:space="preserve"><path d="M0 0h512v512H0z" style="fill: rgb(109, 165, 68);"></path><path d="M256 114.527 448 256 256 397.473 64 256z" style="fill: rgb(255, 218, 68);"></path><circle cx="256" cy="256" r="80.84" style="fill: rgb(240, 240, 240);"></circle><path d="M215.579 250.948c-14.058 0-27.625 2.138-40.395 6.105.565 44.161 36.521 79.79 80.816 79.79 27.39 0 51.58-13.634 66.203-34.471-25.018-31.32-63.515-51.424-106.624-51.424zM335.343 271.488A81.137 81.137 0 0 0 336.842 256c0-44.648-36.194-80.843-80.843-80.843-33.314 0-61.913 20.156-74.29 48.935a166.852 166.852 0 0 1 33.869-3.46c46.957 0 89.433 19.517 119.765 50.856z" style="fill: rgb(0, 82, 180);"></path></svg>',
  104. )]:
  105. "https://upload.wikimedia.org/wikipedia/commons/a/ad/Marijampole_COA.svg",
  106. // Israel / Utena
  107. [normalizeSvgString(
  108. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xml:space="preserve"><path d="M0 0h512v512H0z" style="fill: rgb(240, 240, 240);"></path><path d="M352 200.575h-64.001L256 145.15l-31.998 55.425H160L192.002 256 160 311.425h64.002L256 366.85l31.999-55.425H352L319.998 256 352 200.575zM295.314 256l-19.656 34.048h-39.314L216.686 256l19.657-34.048h39.314L295.314 256zM256 187.903l7.316 12.672h-14.63L256 187.903zm-58.972 34.049h14.632l-7.316 12.672-7.316-12.672zm0 68.096 7.317-12.672 7.316 12.672h-14.633zM256 324.097l-7.315-12.672h14.63L256 324.097zm58.972-34.049H300.34l7.317-12.672 7.315 12.672zm-14.632-68.096h14.632l-7.316 12.672-7.316-12.672zM0 32h512v64H0zM0 416h512v64H0z" style="fill: rgb(0, 82, 180);"></path></svg>',
  109. )]:
  110. "https://upload.wikimedia.org/wikipedia/commons/a/ac/Coat_of_arms_of_Utena_(Lithuania).svg",
  111. // Italy / Alytus
  112. [normalizeSvgString(
  113. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xml:space="preserve"><path d="M341.334 0H0v512h512V0z" style="fill: rgb(240, 240, 240);"></path><path d="M0 0h170.663v512H0z" style="fill: rgb(109, 165, 68);"></path><path d="M341.337 0H512v512H341.337z" style="fill: rgb(216, 0, 39);"></path></svg>',
  114. )]:
  115. "https://upload.wikimedia.org/wikipedia/commons/d/d4/Coat_of_arms_of_Alytus_(Lithuania).svg",
  116. // Germany / Panevėžys
  117. [normalizeSvgString(
  118. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xml:space="preserve"><path d="M0 0h512v512H0z" style="fill: rgb(216, 0, 39);"></path><path d="M0 0h512v170.663H0z"></path><path d="M0 341.337h512V512H0z" style="fill: rgb(255, 218, 68);"></path></svg>',
  119. )]:
  120. "https://upload.wikimedia.org/wikipedia/commons/0/03/Coat_of_Arms_of_Panevezys.svg",
  121. // China / Šiauliai
  122. [normalizeSvgString(
  123. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xml:space="preserve"><path d="M0 0h512v512H0z" style="fill: rgb(216, 0, 39);"></path><path d="m167.619 167.43 19.541 60.143h63.239l-51.161 37.171 19.542 60.143-51.161-37.17-51.162 37.17 19.542-60.143-51.162-37.171h63.239zM290.787 367.465l-19.187-13.94-19.184 13.939 7.327-22.553-19.185-13.94h23.716l7.326-22.553 7.331 22.553h23.713l-19.185 13.939zM340.837 298.576h-23.714l-7.329 22.554-7.327-22.553-23.716-.001 19.187-13.94-7.329-22.552 19.187 13.937 19.185-13.938-7.329 22.553zM340.837 213.426l-19.185 13.94 7.328 22.551-19.184-13.936-19.187 13.938 7.329-22.555-19.186-13.938 23.715-.001 7.329-22.555 7.327 22.555zM290.787 144.536l-7.327 22.555 19.184 13.938-23.712.001-7.33 22.556-7.328-22.557-23.714.002 19.185-13.941-7.329-22.555 19.184 13.941z" style="fill: rgb(255, 218, 68);"></path></svg>',
  124. )]:
  125. "https://upload.wikimedia.org/wikipedia/commons/c/c8/Šiauliai_COA_small.svg",
  126. // France / Klaipėda
  127. [normalizeSvgString(
  128. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xml:space="preserve"><path d="M0 0h512v512H0z" style="fill: rgb(240, 240, 240);"></path><path d="M0 0h170.663v512H0z" style="fill: rgb(0, 82, 180);"></path><path d="M341.337 0H512v512H341.337z" style="fill: rgb(216, 0, 39);"></path></svg>',
  129. )]:
  130. "https://upload.wikimedia.org/wikipedia/commons/4/49/Coat_of_arms_of_Klaipeda_(Lithuania).svg",
  131. // United Kingdom / Kaunas
  132. [normalizeSvgString(
  133. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xml:space="preserve"><path d="M0 0h512v512H0z" style="fill: rgb(240, 240, 240);"></path><path d="M0 304h208v208h96V304h208v-96H304V0h-96v208H0z" style="fill: rgb(216, 0, 39);"></path><path d="M406.92 333.913 512 438.993v-105.08zM333.913 333.913 512 512v-50.36L384.273 333.913zM464.564 512 333.913 381.336V512z" style="fill: rgb(0, 82, 180);"></path><path d="M333.913 333.913 512 512v-50.36L384.273 333.913z" style="fill: rgb(240, 240, 240);"></path><path d="M333.913 333.913 512 512v-50.36L384.273 333.913z" style="fill: rgb(216, 0, 39);"></path><path d="M80.302 333.913 0 414.215v-80.302zM178.084 356.559v155.438H22.658z" style="fill: rgb(0, 82, 180);"></path><path d="M127.724 333.916 0 461.641V512l178.084-178.084z" style="fill: rgb(216, 0, 39);"></path><path d="M105.08 178.087 0 73.007v105.08zM178.087 178.087 0 0v50.36l127.727 127.727zM47.436 0l130.651 130.663V0z" style="fill: rgb(0, 82, 180);"></path><path d="M178.087 178.087 0 0v50.36l127.727 127.727z" style="fill: rgb(240, 240, 240);"></path><path d="M178.087 178.087 0 0v50.36l127.727 127.727z" style="fill: rgb(216, 0, 39);"></path><path d="M431.698 178.087 512 97.785v80.302zM333.916 155.441V.003h155.426z" style="fill: rgb(0, 82, 180);"></path><path d="M384.276 178.084 512 50.359V0L333.916 178.084z" style="fill: rgb(216, 0, 39);"></path></svg>',
  134. )]:
  135. "https://upload.wikimedia.org/wikipedia/commons/3/33/Coat_of_arms_of_Kaunas.svg",
  136. // USA / Vilnius
  137. [normalizeSvgString(
  138. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xml:space="preserve"><path d="M0 0h512v512H0z" style="fill: rgb(240, 240, 240);"></path><path d="M0 64h512v64H0zM0 192h512v64H0zM0 320h512v64H0zM0 448h512v64H0z" style="fill: rgb(216, 0, 39);"></path><path d="M0 0h256v275.69H0z" style="fill: rgb(46, 82, 178);"></path><path d="m51.518 115.318-5.594 17.211H27.826l14.643 10.634-5.594 17.212 14.643-10.634 14.637 10.634-5.595-17.212 14.643-10.634H57.106zM57.106 194.645l-5.588-17.211-5.594 17.211H27.826l14.643 10.634-5.594 17.211 14.643-10.633 14.637 10.633-5.595-17.211 14.643-10.634zM51.518 53.202l-5.594 17.212H27.826l14.643 10.633-5.594 17.212 14.643-10.634 14.637 10.634-5.595-17.212 14.643-10.633H57.106zM128.003 115.318l-5.594 17.211h-18.098l14.643 10.634-5.594 17.212 14.643-10.634 14.637 10.634-5.595-17.212 14.644-10.634h-18.098zM133.591 194.645l-5.588-17.211-5.594 17.211h-18.098l14.643 10.634-5.594 17.211 14.643-10.633 14.637 10.633-5.595-17.211 14.644-10.634zM210.076 194.645l-5.587-17.211-5.595 17.211h-18.097l14.643 10.634-5.595 17.211 14.644-10.633 14.636 10.633-5.594-17.211 14.643-10.634zM204.489 115.318l-5.595 17.211h-18.097l14.643 10.634-5.595 17.212 14.644-10.634 14.636 10.634-5.594-17.212 14.643-10.634h-18.098zM128.003 53.202l-5.594 17.212h-18.098l14.643 10.633-5.594 17.212 14.643-10.634 14.637 10.634-5.595-17.212 14.644-10.633h-18.098zM204.489 53.202l-5.595 17.212h-18.097l14.643 10.633-5.595 17.212 14.644-10.634 14.636 10.634-5.594-17.212 14.643-10.633h-18.098z" style="fill: rgb(240, 240, 240);"></path></svg>',
  139. )]:
  140. "https://upload.wikimedia.org/wikipedia/commons/e/e2/Coat_of_Arms_of_Vilnius.svg",
  141. };
  142.  
  143. const escapeRegExp = (string) => {
  144. return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  145. };
  146.  
  147. const textRegexMap = Object.entries(textReplacements).map(
  148. ([oldName, newName]) => ({
  149. regex: new RegExp("\\b" + escapeRegExp(oldName) + "\\b", "gi"),
  150. newName: newName,
  151. }),
  152. );
  153.  
  154. const processTextNode = (node) => {
  155. let text = node.nodeValue;
  156. let originalText = text;
  157. textRegexMap.forEach((item) => {
  158. text = text.replace(item.regex, item.newName);
  159. });
  160. if (text !== originalText) {
  161. node.nodeValue = text;
  162. }
  163. };
  164.  
  165. const replaceMatchingSvg = (svgElement) => {
  166. if (
  167. !svgElement ||
  168. typeof svgElement.outerHTML !== "string" ||
  169. !svgElement.parentNode
  170. ) {
  171. return false;
  172. }
  173.  
  174. const normalizedOuterHTML = normalizeSvgString(svgElement.outerHTML);
  175. const replacementUrl = svgReplacements[normalizedOuterHTML];
  176.  
  177. if (replacementUrl) {
  178. const img = document.createElement("img");
  179. img.src = replacementUrl;
  180.  
  181. if (svgElement.hasAttribute("class")) {
  182. img.setAttribute("class", svgElement.getAttribute("class"));
  183. }
  184. img.style.display =
  185. window.getComputedStyle(svgElement).display || "inline-block";
  186. img.style.width = "100%";
  187. img.style.height = "100%";
  188. img.style.objectFit = "contain";
  189.  
  190. img.removeAttribute("width");
  191. img.removeAttribute("height");
  192.  
  193. let title = "Lithuanian COA"; // Default title
  194. try {
  195. const urlParts = replacementUrl.split("/");
  196. const filename = decodeURIComponent(urlParts[urlParts.length - 1])
  197. .replace(/\.(svg|png|jpg|jpeg|gif)$/i, "")
  198. .replace(/[_\-]/g, " ")
  199. .replace(/Coat of arms of /i, "")
  200. .replace(/COA/i, "")
  201. .trim();
  202. title =
  203. filename
  204. .split(" ")
  205. .map(
  206. (word) =>
  207. word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
  208. )
  209. .join(" ")
  210. .trim() || title;
  211. } catch (e) {
  212. console.warn(
  213. "Richup.io Replacer: Could not derive title from URL:",
  214. replacementUrl,
  215. e,
  216. );
  217. }
  218. img.setAttribute("title", title);
  219. img.setAttribute("alt", title);
  220.  
  221. svgElement.parentNode.replaceChild(img, svgElement);
  222. return true;
  223. }
  224. return false;
  225. };
  226.  
  227. const walkDOM = (node, processTextFunc, processSvgFunc) => {
  228. const tagName = node.tagName ? node.tagName.toUpperCase() : null;
  229.  
  230. // Skip processing certain tags
  231. if (
  232. tagName === "SCRIPT" ||
  233. tagName === "STYLE" ||
  234. tagName === "TEXTAREA" ||
  235. tagName === "INPUT" ||
  236. tagName === "IMG" // Don't re-process replaced images
  237. ) {
  238. return;
  239. }
  240.  
  241. let nodeReplaced = false;
  242. if (node.nodeType === Node.ELEMENT_NODE) {
  243. if (tagName === "SVG") {
  244. nodeReplaced = processSvgFunc(node);
  245. }
  246. if (!nodeReplaced && node.childNodes) {
  247. // Iterate backwards for live NodeList safety
  248. for (let i = node.childNodes.length - 1; i >= 0; i--) {
  249. walkDOM(node.childNodes[i], processTextFunc, processSvgFunc);
  250. }
  251. }
  252. } else if (node.nodeType === Node.TEXT_NODE) {
  253. if (node.nodeValue && node.nodeValue.trim() !== "") {
  254. processTextFunc(node);
  255. }
  256. }
  257. };
  258.  
  259. const resizeImageContainers = () => {
  260. const targets = document.querySelectorAll(targetSelector);
  261. const resizeAttribute = "data-replacer-resized";
  262.  
  263. targets.forEach((el) => {
  264. const fifthDiv = el.querySelectorAll("div")[4];
  265. if (fifthDiv && !fifthDiv.hasAttribute(resizeAttribute)) {
  266. const children = Array.from(fifthDiv.childNodes).filter(
  267. (node) =>
  268. !(
  269. node.nodeType === Node.TEXT_NODE &&
  270. node.textContent.trim() === ""
  271. ),
  272. );
  273.  
  274. if (
  275. children.length === 1 &&
  276. children[0].nodeType === Node.ELEMENT_NODE &&
  277. children[0].tagName === "IMG"
  278. ) {
  279. fifthDiv.style.width = "2.5rem";
  280. fifthDiv.style.height = "2.5rem";
  281. fifthDiv.style.overflow = "visible";
  282. fifthDiv.setAttribute(resizeAttribute, "true");
  283. }
  284. }
  285. });
  286.  
  287. console.log(
  288. "Richup.io Replacer: One-time resize applied to relevant containers.",
  289. );
  290. };
  291.  
  292. const observerCallback = (mutationsList) => {
  293. for (const mutation of mutationsList) {
  294. if (mutation.type === "childList") {
  295. mutation.addedNodes.forEach((addedNode) => {
  296. if (
  297. addedNode.nodeType === Node.ELEMENT_NODE ||
  298. addedNode.nodeType === Node.TEXT_NODE
  299. ) {
  300. // Only walk the DOM for replacements, resizing is handled separately ONCE.
  301. walkDOM(addedNode, processTextNode, replaceMatchingSvg);
  302. }
  303. });
  304. } else if (mutation.type === "characterData") {
  305. if (mutation.target.nodeType === Node.TEXT_NODE) {
  306. processTextNode(mutation.target);
  307. }
  308. }
  309. // No resizing logic needed in the observer anymore
  310. }
  311. // Check if any *newly added* elements need the one-time resize.
  312. // This covers cases where parts of the UI are added later.
  313. // It still respects the 'data-replacer-resized' attribute.
  314. requestAnimationFrame(resizeImageContainers);
  315. };
  316.  
  317. const observer = new MutationObserver(observerCallback);
  318.  
  319. const observerConfig = {
  320. childList: true,
  321. subtree: true,
  322. characterData: true,
  323. attributeFilter: ["src", "class"], // Still useful to trigger checks if needed, though resizing is marker-based
  324. characterDataOldValue: false,
  325. attributesOldValue: false,
  326. };
  327.  
  328. try {
  329. observer.observe(document.body, observerConfig);
  330. } catch (e) {
  331. console.error("Richup.io Replacer: Failed to start MutationObserver.", e);
  332. }
  333.  
  334. setTimeout(() => {
  335. console.log(
  336. "Richup.io Replacer: Performing initial content scan and ONE-TIME resize...",
  337. );
  338. try {
  339. walkDOM(document.body, processTextNode, replaceMatchingSvg);
  340. resizeImageContainers(); // Apply the resize only once after the initial scan
  341. console.log(
  342. "Richup.io Replacer: Initial scan and resize complete. Observer active.",
  343. );
  344. } catch (e) {
  345. console.error(
  346. "Richup.io Replacer: Error during initial scan/resize.",
  347. e,
  348. );
  349. }
  350. }, 500);
  351. };
  352. })();