Geoguessr Custom Emotes

Allows you to use many custom emotes and some commands in the Geoguessr chat

当前为 2022-10-25 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Geoguessr Custom Emotes
  3. // @description Allows you to use many custom emotes and some commands in the Geoguessr chat
  4. // @version 2.1.3
  5. // @author victheturtle#5159
  6. // @license MIT
  7. // @match https://www.geoguessr.com/*
  8. // @icon https://www.geoguessr.com/_next/static/images/emote-gg-cf17a1f5d51d0ed53f01c65e941beb6d.png
  9. // @namespace https://gf.qytechs.cn/users/967692-victheturtle
  10. // ==/UserScript==
  11.  
  12. let geoguessrCustomEmotes = {};
  13.  
  14. const customEmotesInjectedClass = "custom-emotes-injected";
  15. const getAllNewMessages = () => document.querySelectorAll(`div[class*="chat-message_messageContent__"]:not([class*="${customEmotesInjectedClass}"])`);
  16.  
  17. const _cndic = {};
  18. const hrefset = new Set();
  19. async function scanStyles() {
  20. for (let node of document.querySelectorAll('head link[rel="preload"], head style[data-n-href*=".css"]')) {
  21. const href = node.href || location.origin+node.dataset.nHref;
  22. if (hrefset.has(href)) continue;
  23. hrefset.add(href);
  24. await fetch(href)
  25. .then(res => res.text())
  26. .then(stylesheet => {
  27. for (let className of stylesheet.split(".")) {
  28. const ind = className.indexOf("__");
  29. if (ind != -1) _cndic[className.substr(0, ind+2)] = className.substr(0, ind+7);
  30. };
  31. });
  32. };
  33. }
  34. const cn = (classNameStart) => _cndic[classNameStart]; // cn("status_section__") -> "status_section__8uP8o"
  35.  
  36. const emoteInjectionTemplate = (emoteSrc) => `</span>
  37. <span class="${cn("chat-message_emoteWrapper__")}"><img src="${emoteSrc}" class="${cn("chat-message_messageEmote__")}"></span>
  38. <span class="${cn("chat-message_messageText__")}">`;
  39.  
  40. async function fetchWithCors(url, method, body) {
  41. return await fetch(url, {
  42. "headers": {
  43. "accept": "*/*",
  44. "accept-language": "en-US,en;q=0.8",
  45. "content-type": "application/json",
  46. "sec-fetch-dest": "empty",
  47. "sec-fetch-mode": "cors",
  48. "sec-fetch-site": "same-site",
  49. "sec-gpc": "1",
  50. "x-client": "web"
  51. },
  52. "referrer": "https://www.geoguessr.com/",
  53. "referrerPolicy": "strict-origin-when-cross-origin",
  54. "body": (method == "GET") ? null : JSON.stringify(body),
  55. "method": method,
  56. "mode": "cors",
  57. "credentials": "include"
  58. });
  59. };
  60.  
  61. const getGameId = () => location.pathname.split("/")[2];
  62. const getPartyId = async () => await fetchWithCors(getLobbyApi(getGameId()), "POST", {})
  63. .then(it => it.json()).then(it => it.partyId);
  64. const getPlayerId = async (nick) => await fetchWithCors(getLobbyApi(getGameId()), "POST", {})
  65. .then(it => it.json()).then(it => {
  66. let matches = it.players.filter(it => it.nick.toLowerCase() == nick.toLowerCase()).map(it => it.playerId).sort();
  67. return matches[matches.length-1];
  68. });
  69. const getLobbyApi = (gameId) => `https://game-server.geoguessr.com/api/lobby/${gameId}/join`;
  70. const getKickApi = (gameId) => `https://game-server.geoguessr.com/api/lobby/${gameId}/kick`;
  71. const getBanApi = (partyId) => `https://www.geoguessr.com/api/v4/parties/${partyId}/ban`;
  72. const getRoundNumberApi = (gameId) => `https://game-server.geoguessr.com/api/duels/${gameId}/`;
  73. const getRoundNumber = async () => await fetchWithCors(getRoundNumberApi(getGameId()), "GET")
  74. .then(it => it.json()).then(it => it.currentRoundNumber);
  75. const getGuessApi = (gameId) => `https://game-server.geoguessr.com/api/duels/${gameId}/guess`;
  76.  
  77. async function ban(nick) {
  78. const playerId = await getPlayerId(nick);
  79. const partyId = await getPartyId();
  80. fetchWithCors(getKickApi(getGameId()), "POST", {playerId: playerId}).catch(e => console.log(e));
  81. fetchWithCors(getBanApi(partyId), "POST", {userId: playerId, ban: true}).catch(e => console.log(e));
  82. };
  83.  
  84. async function unban(nick) {
  85. const playerId = await getPlayerId(nick);
  86. const partyId = await getPartyId();
  87. fetchWithCors(getBanApi(partyId), "POST", {userId: playerId, ban: false}).catch(e => console.log(e));
  88. };
  89.  
  90. async function openProfile(nick) {
  91. const playerId = await getPlayerId(nick);
  92. window.open("/user/"+playerId);
  93. };
  94.  
  95. async function guessEiffelTower() {
  96. const rn = await getRoundNumber();
  97. fetchWithCors(getGuessApi(getGameId()), "POST", {"lat": 48.85837, "lng": 2.29448, "roundNumber": rn}).catch(e => console.log(e));
  98. };
  99.  
  100. function handleCommand(type, args, isSelf) {
  101. try {
  102. console.log(type)
  103. console.log(args)
  104. if (type == "/ban") {
  105. if (args.length != 0 && isSelf) ban(args);
  106. } else if (type == "/unban") {
  107. if (args.length != 0 && isSelf) unban(args);
  108. } else if (type == "/mute") {
  109. if (args.length != 0 && isSelf) localStorage.setItem("CustomEmotesMuted"+args.toLowerCase(), "1");
  110. } else if (type == "/unmute") {
  111. if (args.length != 0 && isSelf) localStorage.setItem("CustomEmotesMuted"+args.toLowerCase(), "0");
  112. } else if (type == "/check") {
  113. if (args.length != 0 && isSelf) openProfile(args);
  114. } else if (type == "/eiffel") {
  115. if (location.pathname.includes("duel") && isSelf) guessEiffelTower();
  116. }
  117. } catch (e) { console.log(e); };
  118. };
  119.  
  120. function injectCustomEmotes(words) {
  121. for (let i=0; i<words.length; i+=2) {
  122. if (words[i] == "") continue;
  123. const lowercaseWord = words[i].toLowerCase();
  124. for (let emoteName in geoguessrCustomEmotes) {
  125. if (lowercaseWord == emoteName.toLowerCase() || lowercaseWord[0] == ":" && lowercaseWord == ":"+emoteName.toLowerCase()+":") {
  126. words[i] = emoteInjectionTemplate(geoguessrCustomEmotes[emoteName]);
  127. break;
  128. }
  129. }
  130. }
  131. return words.join("");
  132. }
  133.  
  134. let observer = new MutationObserver((mutations) => {
  135. const newMessages = getAllNewMessages();
  136. if (newMessages.length == 0) return;
  137. scanStyles().then(() => {
  138. for (let message of newMessages) {
  139. if (message.classList.contains(customEmotesInjectedClass)) continue;
  140. message.classList.add(customEmotesInjectedClass);
  141. const words = message.innerHTML.split(/((?:<|>|&lt;|&gt;|,| |\.)+)/g);
  142. const author = message.innerHTML.split(/(?:<|>)+/)[2];
  143. const messageContentStart = words.indexOf("><");
  144. const isSelf = message.parentNode.className.includes("isSelf");
  145. if (!isSelf && localStorage.getItem("CustomEmotesMuted"+author.toLowerCase()) == "1") {
  146. message.innerHTML = words.slice(0, messageContentStart+5).join("") + "[Muted]" + words.slice(words.length-4).join("");
  147. } else {
  148. if (words.length >= messageContentStart+10 && words[messageContentStart+5][0] == "/") {
  149. handleCommand(words[messageContentStart+5], words.slice(messageContentStart+7, words.length-4).join(""), isSelf)
  150. }
  151. message.innerHTML = injectCustomEmotes(words);
  152. }
  153. }})
  154. });
  155.  
  156. async function fetchEmotesRepository() {
  157. const lastTimeFetched = localStorage.getItem("CustomEmotesLastFetched")*1
  158. if (Date.now() - lastTimeFetched < 60*1000) { // Github API has a limit rate of 60 requests per hour so prevent more than 1 request per minute
  159. return localStorage.getItem("CustomEmotesStored")
  160. } else {
  161. const emotesRepositoryContent = await fetch("https://api.github.com/gists/7e5046589b0f020c1ec80629c582cca6")
  162. .then(it => it.json())
  163. .then(it => it.files["GeoguessrCustomEmotesRepository.json"].content);
  164. localStorage.setItem("CustomEmotesStored", emotesRepositoryContent);
  165. localStorage.setItem("CustomEmotesLastFetched", Date.now());
  166. return emotesRepositoryContent;
  167. }
  168. }
  169.  
  170. (() => {
  171. fetchEmotesRepository().then(emotesRepositoryContent => {
  172. geoguessrCustomEmotes = JSON.parse(emotesRepositoryContent);
  173. observer.observe(document.body, { subtree: true, childList: true });
  174. }).catch(err => console.log(`Geoguessr Custom Emotes error at fetchEmotesRepository(): ${err}`));
  175. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址