Greasy Fork 支持简体中文。

Wrong Country Streak Counter

Adds a country streak counter to the GeoGuessr website

  1. // ==UserScript==
  2. // @name Wrong Country Streak Counter
  3. // @version 1.0.0
  4. // @description Adds a country streak counter to the GeoGuessr website
  5. // @match https://www.geoguessr.com/*
  6. // @author victheturtle#5159, DeSlagen
  7. // @license MIT
  8. // @grant none
  9. // @namespace https://greasyfork.org/users/1223042
  10. // ==/UserScript==
  11.  
  12. const AUTOMATIC = true;
  13. // ^^^^ Replace with false for a manual counter
  14.  
  15. const API_Key = 'INSERT_BIGDATACLOUD_API_KEY_HERE';
  16. // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Replace INSERT_BIGDATACLOUD_API_KEY_HERE with your API key (keep the quote marks)
  17. // THIS IS OPTIONAL: if you don't provide an API key, the script will use another method to get the country
  18.  
  19. const CountryDict = {
  20. AF: 'AF',
  21. AX: 'FI', // Aland Islands
  22. AL: 'AL',
  23. DZ: 'DZ',
  24. AS: 'US', // American Samoa
  25. AD: 'AD',
  26. AO: 'AO',
  27. AI: 'GB', // Anguilla
  28. AQ: 'AQ', // Antarctica
  29. AG: 'AG',
  30. AR: 'AR',
  31. AM: 'AM',
  32. AW: 'NL', // Aruba
  33. AU: 'AU',
  34. AT: 'AT',
  35. AZ: 'AZ',
  36. BS: 'BS',
  37. BH: 'BH',
  38. BD: 'BD',
  39. BB: 'BB',
  40. BY: 'BY',
  41. BE: 'BE',
  42. BZ: 'BZ',
  43. BJ: 'BJ',
  44. BM: 'GB', // Bermuda
  45. BT: 'BT',
  46. BO: 'BO',
  47. BQ: 'NL', // Bonaire, Sint Eustatius, Saba
  48. BA: 'BA',
  49. BW: 'BW',
  50. BV: 'NO', // Bouvet Island
  51. BR: 'BR',
  52. IO: 'GB', // British Indian Ocean Territory
  53. BN: 'BN',
  54. BG: 'BG',
  55. BF: 'BF',
  56. BI: 'BI',
  57. KH: 'KH',
  58. CM: 'CM',
  59. CA: 'CA',
  60. CV: 'CV',
  61. KY: 'UK', // Cayman Islands
  62. CF: 'CF',
  63. TD: 'TD',
  64. CL: 'CL',
  65. CN: 'CN',
  66. CX: 'AU', // Christmas Islands
  67. CC: 'AU', // Cocos (Keeling) Islands
  68. CO: 'CO',
  69. KM: 'KM',
  70. CG: 'CG',
  71. CD: 'CD',
  72. CK: 'NZ', // Cook Islands
  73. CR: 'CR',
  74. CI: 'CI',
  75. HR: 'HR',
  76. CU: 'CU',
  77. CW: 'NL', // Curacao
  78. CY: 'CY',
  79. CZ: 'CZ',
  80. DK: 'DK',
  81. DJ: 'DJ',
  82. DM: 'DM',
  83. DO: 'DO',
  84. EC: 'EC',
  85. EG: 'EG',
  86. SV: 'SV',
  87. GQ: 'GQ',
  88. ER: 'ER',
  89. EE: 'EE',
  90. ET: 'ET',
  91. FK: 'GB', // Falkland Islands
  92. FO: 'DK', // Faroe Islands
  93. FJ: 'FJ',
  94. FI: 'FI',
  95. FR: 'FR',
  96. GF: 'FR', // French Guiana
  97. PF: 'FR', // French Polynesia
  98. TF: 'FR', // French Southern Territories
  99. GA: 'GA',
  100. GM: 'GM',
  101. GE: 'GE',
  102. DE: 'DE',
  103. GH: 'GH',
  104. GI: 'UK', // Gibraltar
  105. GR: 'GR',
  106. GL: 'DK', // Greenland
  107. GD: 'GD',
  108. GP: 'FR', // Guadeloupe
  109. GU: 'US', // Guam
  110. GT: 'GT',
  111. GG: 'GB', // Guernsey
  112. GN: 'GN',
  113. GW: 'GW',
  114. GY: 'GY',
  115. HT: 'HT',
  116. HM: 'AU', // Heard Island and McDonald Islands
  117. VA: 'VA',
  118. HN: 'HN',
  119. HK: 'CN', // Hong Kong
  120. HU: 'HU',
  121. IS: 'IS',
  122. IN: 'IN',
  123. ID: 'ID',
  124. IR: 'IR',
  125. IQ: 'IQ',
  126. IE: 'IE',
  127. IM: 'GB', // Isle of Man
  128. IL: 'IL',
  129. IT: 'IT',
  130. JM: 'JM',
  131. JP: 'JP',
  132. JE: 'GB', // Jersey
  133. JO: 'JO',
  134. KZ: 'KZ',
  135. KE: 'KE',
  136. KI: 'KI',
  137. KR: 'KR',
  138. KW: 'KW',
  139. KG: 'KG',
  140. LA: 'LA',
  141. LV: 'LV',
  142. LB: 'LB',
  143. LS: 'LS',
  144. LR: 'LR',
  145. LY: 'LY',
  146. LI: 'LI',
  147. LT: 'LT',
  148. LU: 'LU',
  149. MO: 'CN', // Macao
  150. MK: 'MK',
  151. MG: 'MG',
  152. MW: 'MW',
  153. MY: 'MY',
  154. MV: 'MV',
  155. ML: 'ML',
  156. MT: 'MT',
  157. MH: 'MH',
  158. MQ: 'FR', // Martinique
  159. MR: 'MR',
  160. MU: 'MU',
  161. YT: 'FR', // Mayotte
  162. MX: 'MX',
  163. FM: 'FM',
  164. MD: 'MD',
  165. MC: 'MC',
  166. MN: 'MN',
  167. ME: 'ME',
  168. MS: 'GB', // Montserrat
  169. MA: 'MA',
  170. MZ: 'MZ',
  171. MM: 'MM',
  172. NA: 'NA',
  173. NR: 'NR',
  174. NP: 'NP',
  175. NL: 'NL',
  176. AN: 'NL', // Netherlands Antilles
  177. NC: 'FR', // New Caledonia
  178. NZ: 'NZ',
  179. NI: 'NI',
  180. NE: 'NE',
  181. NG: 'NG',
  182. NU: 'NZ', // Niue
  183. NF: 'AU', // Norfolk Island
  184. MP: 'US', // Northern Mariana Islands
  185. NO: 'NO',
  186. OM: 'OM',
  187. PK: 'PK',
  188. PW: 'PW',
  189. PS: 'IL', // Palestine
  190. PA: 'PA',
  191. PG: 'PG',
  192. PY: 'PY',
  193. PE: 'PE',
  194. PH: 'PH',
  195. PN: 'GB', // Pitcairn
  196. PL: 'PL',
  197. PT: 'PT',
  198. PR: 'US', // Puerto Rico
  199. QA: 'QA',
  200. RE: 'FR', // Reunion
  201. RO: 'RO',
  202. RU: 'RU',
  203. RW: 'RW',
  204. BL: 'FR', // Saint Barthelemy
  205. SH: 'GB', // Saint Helena
  206. KN: 'KN',
  207. LC: 'LC',
  208. MF: 'FR', // Saint Martin
  209. PM: 'FR', // Saint Pierre and Miquelon
  210. VC: 'VC',
  211. WS: 'WS',
  212. SM: 'SM',
  213. ST: 'ST',
  214. SA: 'SA',
  215. SN: 'SN',
  216. RS: 'RS',
  217. SC: 'SC',
  218. SL: 'SL',
  219. SG: 'SG',
  220. SX: 'NL', // Sint Maarten
  221. SK: 'SK',
  222. SI: 'SI',
  223. SB: 'SB',
  224. SO: 'SO',
  225. ZA: 'ZA',
  226. GS: 'GB', // South Georgia and the South Sandwich Islands
  227. ES: 'ES',
  228. LK: 'LK',
  229. SD: 'SD',
  230. SR: 'SR',
  231. SJ: 'NO', // Svalbard and Jan Mayen
  232. SZ: 'SZ',
  233. SE: 'SE',
  234. CH: 'CH',
  235. SY: 'SY',
  236. TW: 'TW', // Taiwan
  237. TJ: 'TJ',
  238. TZ: 'TZ',
  239. TH: 'TH',
  240. TL: 'TL',
  241. TG: 'TG',
  242. TK: 'NZ', // Tokelau
  243. TO: 'TO',
  244. TT: 'TT',
  245. TN: 'TN',
  246. TR: 'TR',
  247. TM: 'TM',
  248. TC: 'GB', // Turcs and Caicos Islands
  249. TV: 'TV',
  250. UG: 'UG',
  251. UA: 'UA',
  252. AE: 'AE',
  253. GB: 'GB',
  254. US: 'US',
  255. UM: 'US', // US Minor Outlying Islands
  256. UY: 'UY',
  257. UZ: 'UZ',
  258. VU: 'VU',
  259. VE: 'VE',
  260. VN: 'VN',
  261. VG: 'GB', // British Virgin Islands
  262. VI: 'US', // US Virgin Islands
  263. WF: 'FR', // Wallis and Futuna
  264. EH: 'MA', // Western Sahara
  265. YE: 'YE',
  266. ZM: 'ZM',
  267. ZW: 'ZW'
  268. };
  269.  
  270. const ERROR_RESP = -1000000;
  271. let streak = parseInt(sessionStorage.getItem("Streak") || 0, 10);
  272.  
  273. function checkGameMode() {
  274. return location.pathname.includes("/game/") || location.pathname.includes("/challenge/");
  275. };
  276.  
  277. var style = document.createElement("style");
  278. document.head.appendChild(style);
  279. style.sheet.insertRule("div[class*='round-result_distanceIndicatorWrapper__'] { animation-delay: 0s, 0s; animation-duration: 0s, 0s; grid-area: 1 / 1 / span 1 / span 1; margin-right: 28px }")
  280. style.sheet.insertRule("div[class*='round-result_actions__'] { animation-delay: 0s; animation-duration: 0s; grid-area: 2 / 1 / span 1 / span 3; margin: 0px; margin-top: 10px; margin-bottom: 10px }")
  281. style.sheet.insertRule("div[class*='round-result_pointsIndicatorWrapper__'] { animation-delay: 0s, 0s; animation-duration: 0s, 0s; grid-area: 1 / 2 / span 1 / span 1; margin-right: 28px }")
  282. style.sheet.insertRule("div[class*='map-pin_largeMapPin__'] { height: 2rem; width: 2rem; margin-left: -1rem; margin-top: -1rem }")
  283. style.sheet.insertRule("p[class*='round-result_label__'] { display: none }")
  284. style.sheet.insertRule("div[class*='results-confetti_wrapper__'] { visibility: hidden }")
  285. style.sheet.insertRule("div[class*='round-result_wrapper__'] { align-self: center; display: grid; flex-wrap: wrap }")
  286. style.sheet.insertRule("div[class*='result-layout_contentNew__'] { display: flex; justify-content: center }")
  287. style.sheet.insertRule("p[class*='standard-final-result_spacebarLabel__'] { display: none }")
  288. style.sheet.insertRule("div[class*='standard-final-result_wrapper__'] { align-items: normal; justify-content: center }")
  289. style.sheet.insertRule("div[class*='round-result_topPlayersButton__'] { position: absolute; bottom: 9rem }")
  290. style.sheet.insertRule("div[class*='shadow-text_positiveTextShadow_CUSTOM_1_'] { text-shadow: 0 .25rem 0 var(--ds-color-black-50),.125rem .125rem .5rem var(--ds-color-green-50),0 -.25rem .5rem var(--ds-color-green-50),-.25rem .5rem .5rem #77df9b,0 0.375rem 2rem var(--ds-color-green-50),0 0 0 var(--ds-color-green-50),0 0 1.5rem rgba(161,155,217,.65),.25rem .25rem 1rem var(--ds-color-green-50) }")
  291. style.sheet.insertRule("div[class*='shadow-text_negativeTextShadow_CUSTOM_1_'] { text-shadow: 0 .25rem 0 var(--ds-color-black-50),.125rem .125rem .5rem var(--ds-color-red-50),0 -.25rem .5rem var(--ds-color-red-50),-.25rem .5rem .5rem #b45862,0 0.375rem 2rem var(--ds-color-red-50),0 0 0 var(--ds-color-red-50),0 0 1.5rem rgba(161,155,217,.65),.25rem .25rem 1rem var(--ds-color-red-50) }")
  292. style.sheet.insertRule("a[href*='github'] { display: none }");
  293.  
  294. function addStreakStatusBar() {
  295. const status_length = document.getElementsByClassName(cn("status_section__")).length;
  296. if (document.getElementById("country-streak") == null && status_length >= 3) {
  297. const newDiv = document.createElement("div");
  298. newDiv.className = cn('status_section__');
  299. newDiv.innerHTML = `<div class="${cn("status_label__")}">Streak</div>
  300. <div id="country-streak" class="${cn("status_value__")}">${streak}</div>`;
  301. const statusBar = document.getElementsByClassName(cn("status_inner__"))[0];
  302. statusBar.insertBefore(newDiv, statusBar.children[3]);
  303. };
  304. };
  305.  
  306. const newFormat = (streak, positive) => `
  307. <div class="${cn("round-result_distanceUnitIndicator__")}">
  308. <div class="${cn("shadow-text_root__")} shadow-text_${(!positive || streak == 0) ? "negative" : "positive"}TextShadow_CUSTOM_1_ ${cn("shadow-text_sizeSmallMedium__")}">${(!positive) ? "Lost at" : "Streak"}&nbsp;</div>
  309. </div>
  310. <div class="${cn("shadow-text_root__")} shadow-text_${(!positive || streak == 0) ? "negative" : "positive"}TextShadow_CUSTOM_1_ ${cn("shadow-text_sizeSmallMedium__")}">
  311. <div><div>${streak}</div></div>
  312. </div>
  313. `
  314.  
  315. const newFormatSummary = (streak, positive) => `
  316. <div class="${cn("round-result_distanceUnitIndicator__")}">
  317. <div class="${cn("shadow-text_root__")} shadow-text_${(!positive || streak == 0) ? "negative" : "positive"}TextShadow_CUSTOM_1_ ${cn("shadow-text_sizeSmallMedium__")}">${(!positive) ? "Wrong streak at" : "Country streak"}&nbsp;</div>
  318. </div>
  319. <div class="${cn("shadow-text_root__")} shadow-text_${(!positive || streak == 0) ? "negative" : "positive"}TextShadow_CUSTOM_1_ ${cn("shadow-text_sizeSmallMedium__")}">
  320. <div><div>${streak}</div></div>
  321. </div>
  322. `
  323.  
  324. function addStreakRoundResult() {
  325. if (document.getElementById("country-streak2") == null && !!document.querySelector('div[class*="round-result_distanceIndicatorWrapper__"]')) {
  326. const newDiv = document.createElement("div");
  327. newDiv.innerHTML = `<div id="country-streak2" class="${cn("round-result_distanceWrapper__")}">${newFormat(streak, true)}</div>`;
  328. newDiv.style = "grid-area: 1 / 3 / span 1 / span 1; ";
  329. document.querySelector('div[class*="round-result_wrapper__"]').appendChild(newDiv);
  330. };
  331. };
  332.  
  333. function addStreakGameSummary() {
  334. if (document.getElementById("country-streak3") == null && !!document.querySelector('div[class*="result-overlay_overlayTotalScore__"]')
  335. /*&& !document.querySelector('div[class*="result-overlay_overlayQuickPlayProgress__"]')*/) {
  336. const newDiv = document.createElement("div");
  337. newDiv.innerHTML = `<div id="country-streak3" class="${cn("round-result_distanceWrapper__")}">${newFormatSummary(streak, true)}</div>`;
  338. newDiv.style = "display: flex; align-items: center;";
  339. const totalScore = document.querySelector('div[class*="result-overlay_overlayTotalScore__"]');
  340. totalScore.parentNode.insertBefore(newDiv, totalScore.parentNode.children[1]);
  341. totalScore.style.marginTop = "10px";
  342. };
  343. };
  344.  
  345. function updateStreak(newStreak) {
  346. if (newStreak === ERROR_RESP) {
  347. if (document.getElementById("country-streak2") != null && !!document.querySelector('div[class*="round-result_distanceIndicatorWrapper__"]')) {
  348. document.getElementById("country-streak2").innerHTML = "";
  349. }
  350. return;
  351. }
  352. sessionStorage.setItem("Wrong Streak", newStreak);
  353. if (!(streak > 0 && newStreak == 0)) {
  354. sessionStorage.setItem("StreakBackup", newStreak);
  355. };
  356. if (document.getElementById("country-streak") != null) {
  357. document.getElementById("country-streak").innerHTML = newStreak;
  358. };
  359. if (document.getElementById("country-streak2") != null) {
  360. document.getElementById("country-streak2").innerHTML = newFormat(newStreak, true);
  361. if (newStreak == 0 && streak > 0) {
  362. document.getElementById("country-streak2").innerHTML = newFormat(streak, false);
  363. };
  364. };
  365. if (document.getElementById("country-streak3") != null) {
  366. document.getElementById("country-streak3").innerHTML = newFormatSummary(newStreak, true);
  367. if (newStreak == 0 && streak > 0) {
  368. document.getElementById("country-streak3").innerHTML = newFormatSummary(streak, false);
  369. };
  370. };
  371. streak = newStreak;
  372. };
  373.  
  374. async function getCountryCode(coords) {
  375. if (coords[0] <= -85.05) return 'AQ';
  376. if (API_Key.toLowerCase().match("^(bdc_)?[a-f0-9]{32}$") != null) {
  377. const api = "https://api.bigdatacloud.net/data/reverse-geocode?latitude="+coords.lat+"&longitude="+coords.lng+"&localityLanguage=en&key="+API_Key;
  378. return await fetch(api)
  379. .then(res => (res.status !== 200) ? ERROR_RESP : res.json())
  380. .then(out => (out === ERROR_RESP) ? ERROR_RESP : CountryDict[out.countryCode]);
  381. } else {
  382. const api = `https://nominatim.openstreetmap.org/reverse.php?lat=${coords.lat}&lon=${coords.lng}&zoom=21&format=jsonv2&accept-language=en`;
  383. return await fetch(api)
  384. .then(res => (res.status !== 200) ? ERROR_RESP : res.json())
  385. .then(out => (out === ERROR_RESP) ? ERROR_RESP : CountryDict[out?.address?.country_code?.toUpperCase()]);
  386. }
  387. };
  388.  
  389. let lastGuess = { lat: 91, lng: 0 };
  390. function check() {
  391. const gameTag = location.href.substring(location.href.lastIndexOf('/') + 1)
  392. let apiUrl = "https://www.geoguessr.com/api/v3/games/"+gameTag;
  393. if (location.pathname.includes("/challenge/")) {
  394. apiUrl = "https://www.geoguessr.com/api/v3/challenges/"+gameTag+"/game";
  395. };
  396. fetch(apiUrl)
  397. .then(res => res.json())
  398. .then((out) => {
  399. const guessCounter = out.player.guesses.length;
  400. const round = out.rounds[guessCounter-1];
  401. const guess = out.player.guesses[guessCounter-1];
  402. if (guess.lat == lastGuess.lat && guess.lng == lastGuess.lng) return;
  403. lastGuess = guess;
  404. Promise.all([getCountryCode(guess), getCountryCode(round)]).then(codes => {
  405. if (codes[0] == ERROR_RESP || codes[1] == ERROR_RESP) {
  406. updateStreak(ERROR_RESP);
  407. } else if (codes[0] == codes[1]) {
  408. updateStreak(0);
  409. } else {
  410. updateStreak(streak + 1);
  411. };
  412. });
  413. }).catch(err => { throw err });
  414. };
  415.  
  416. function doCheck() {
  417. if (!document.querySelector('div[class*="result-layout_root__"]')) {
  418. sessionStorage.setItem("Checked", 0);
  419. } else if ((sessionStorage.getItem("Checked") || 0) == 0) {
  420. check();
  421. sessionStorage.setItem("Checked", 1);
  422. }
  423. };
  424.  
  425. let lastDoCheckCall = 0;
  426. new MutationObserver(async (mutations) => {
  427. if (!checkGameMode() || lastDoCheckCall >= (Date.now() - 50)) return;
  428. lastDoCheckCall = Date.now();
  429. await scanStyles()
  430. if (AUTOMATIC) doCheck();
  431. addStreakStatusBar();
  432. addStreakRoundResult();
  433. addStreakGameSummary();
  434. }).observe(document.body, { subtree: true, childList: true });
  435.  
  436. document.addEventListener('keypress', (e) => {
  437. if (e.key == '1') {
  438. updateStreak(streak + 1);
  439. } else if (e.key == '2') {
  440. updateStreak(streak - 1);
  441. } else if (e.key == '8') {
  442. const streakBackup = parseInt(sessionStorage.getItem("StreakBackup") || 0, 10);
  443. updateStreak(streakBackup + 1);
  444. } else if (e.key == '0') {
  445. updateStreak(0);
  446. sessionStorage.setItem("StreakBackup", 0);
  447. };
  448. });