Country Streak Counter

Adds a country streak counter to the GeoGuessr website

目前為 2022-10-09 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Country Streak Counter
  3. // @version 1.3.2
  4. // @description Adds a country streak counter to the GeoGuessr website
  5. // @match https://www.geoguessr.com/*
  6. // @author victheturtle#5159
  7. // @license MIT
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
  9. // @namespace https://gf.qytechs.cn/users/967692-victheturtle
  10. // ==/UserScript==
  11.  
  12. // Credits to subsymmetry for the original version of the Streak Counter
  13.  
  14. let ENABLED_ON_CHALLENGES = false; //Replace with true or false
  15. let API_Key = 'ENTER_API_KEY_HERE'; //Replace ENTER_API_KEY_HERE with your API key (so keep the quote marks)
  16. let AUTOMATIC = true; //Replace with false for a manual counter. Without an API key, the counter will still be manual
  17.  
  18.  
  19. if (sessionStorage.getItem("Streak") == null) {
  20. sessionStorage.setItem("Streak", 0);
  21. };
  22. if (sessionStorage.getItem("StreakBackup") == null) {
  23. sessionStorage.setItem("StreakBackup", 0);
  24. };
  25. if (sessionStorage.getItem("Checked") == null) {
  26. sessionStorage.setItem("Checked", 0);
  27. };
  28.  
  29. let streak = parseInt(sessionStorage.getItem("Streak"), 10);
  30. let last_guess = [0,0];
  31. const ERROR_RESP = -1000000;
  32.  
  33. var CountryDict = {
  34. AF: 'AF',
  35. AX: 'FI', // Aland Islands
  36. AL: 'AL',
  37. DZ: 'DZ',
  38. AS: 'US', // American Samoa
  39. AD: 'AD',
  40. AO: 'AO',
  41. AI: 'GB', // Anguilla
  42. AQ: 'AQ', // Antarctica
  43. AG: 'AG',
  44. AR: 'AR',
  45. AM: 'AM',
  46. AW: 'NL', // Aruba
  47. AU: 'AU',
  48. AT: 'AT',
  49. AZ: 'AZ',
  50. BS: 'BS',
  51. BH: 'BH',
  52. BD: 'BD',
  53. BB: 'BB',
  54. BY: 'BY',
  55. BE: 'BE',
  56. BZ: 'BZ',
  57. BJ: 'BJ',
  58. BM: 'GB', // Bermuda
  59. BT: 'BT',
  60. BO: 'BO',
  61. BQ: 'NL', // Bonaire, Sint Eustatius, Saba
  62. BA: 'BA',
  63. BW: 'BW',
  64. BV: 'NO', // Bouvet Island
  65. BR: 'BR',
  66. IO: 'GB', // British Indian Ocean Territory
  67. BN: 'BN',
  68. BG: 'BG',
  69. BF: 'BF',
  70. BI: 'BI',
  71. KH: 'KH',
  72. CM: 'CM',
  73. CA: 'CA',
  74. CV: 'CV',
  75. KY: 'UK', // Cayman Islands
  76. CF: 'CF',
  77. TD: 'TD',
  78. CL: 'CL',
  79. CN: 'CN',
  80. CX: 'AU', // Christmas Islands
  81. CC: 'AU', // Cocos (Keeling) Islands
  82. CO: 'CO',
  83. KM: 'KM',
  84. CG: 'CG',
  85. CD: 'CD',
  86. CK: 'NZ', // Cook Islands
  87. CR: 'CR',
  88. CI: 'CI',
  89. HR: 'HR',
  90. CU: 'CU',
  91. CW: 'NL', // Curacao
  92. CY: 'CY',
  93. CZ: 'CZ',
  94. DK: 'DK',
  95. DJ: 'DJ',
  96. DM: 'DM',
  97. DO: 'DO',
  98. EC: 'EC',
  99. EG: 'EG',
  100. SV: 'SV',
  101. GQ: 'GQ',
  102. ER: 'ER',
  103. EE: 'EE',
  104. ET: 'ET',
  105. FK: 'GB', // Falkland Islands
  106. FO: 'DK', // Faroe Islands
  107. FJ: 'FJ',
  108. FI: 'FI',
  109. FR: 'FR',
  110. GF: 'FR', // French Guiana
  111. PF: 'FR', // French Polynesia
  112. TF: 'FR', // French Southern Territories
  113. GA: 'GA',
  114. GM: 'GM',
  115. GE: 'GE',
  116. DE: 'DE',
  117. GH: 'GH',
  118. GI: 'UK', // Gibraltar
  119. GR: 'GR',
  120. GL: 'DK', // Greenland
  121. GD: 'GD',
  122. GP: 'FR', // Guadeloupe
  123. GU: 'US', // Guam
  124. GT: 'GT',
  125. GG: 'GB', // Guernsey
  126. GN: 'GN',
  127. GW: 'GW',
  128. GY: 'GY',
  129. HT: 'HT',
  130. HM: 'AU', // Heard Island and McDonald Islands
  131. VA: 'VA',
  132. HN: 'HN',
  133. HK: 'CN', // Hong Kong
  134. HU: 'HU',
  135. IS: 'IS',
  136. IN: 'IN',
  137. ID: 'ID',
  138. IR: 'IR',
  139. IQ: 'IQ',
  140. IE: 'IE',
  141. IM: 'GB', // Isle of Man
  142. IL: 'IL',
  143. IT: 'IT',
  144. JM: 'JM',
  145. JP: 'JP',
  146. JE: 'GB', // Jersey
  147. JO: 'JO',
  148. KZ: 'KZ',
  149. KE: 'KE',
  150. KI: 'KI',
  151. KR: 'KR',
  152. KW: 'KW',
  153. KG: 'KG',
  154. LA: 'LA',
  155. LV: 'LV',
  156. LB: 'LB',
  157. LS: 'LS',
  158. LR: 'LR',
  159. LY: 'LY',
  160. LI: 'LI',
  161. LT: 'LT',
  162. LU: 'LU',
  163. MO: 'CN', // Macao
  164. MK: 'MK',
  165. MG: 'MG',
  166. MW: 'MW',
  167. MY: 'MY',
  168. MV: 'MV',
  169. ML: 'ML',
  170. MT: 'MT',
  171. MH: 'MH',
  172. MQ: 'FR', // Martinique
  173. MR: 'MR',
  174. MU: 'MU',
  175. YT: 'FR', // Mayotte
  176. MX: 'MX',
  177. FM: 'FM',
  178. MD: 'MD',
  179. MC: 'MC',
  180. MN: 'MN',
  181. ME: 'ME',
  182. MS: 'GB', // Montserrat
  183. MA: 'MA',
  184. MZ: 'MZ',
  185. MM: 'MM',
  186. NA: 'NA',
  187. NR: 'NR',
  188. NP: 'NP',
  189. NL: 'NL',
  190. AN: 'NL', // Netherlands Antilles
  191. NC: 'FR', // New Caledonia
  192. NZ: 'NZ',
  193. NI: 'NI',
  194. NE: 'NE',
  195. NG: 'NG',
  196. NU: 'NZ', // Niue
  197. NF: 'AU', // Norfolk Island
  198. MP: 'US', // Northern Mariana Islands
  199. NO: 'NO',
  200. OM: 'OM',
  201. PK: 'PK',
  202. PW: 'PW',
  203. PS: 'IL', // Palestine
  204. PA: 'PA',
  205. PG: 'PG',
  206. PY: 'PY',
  207. PE: 'PE',
  208. PH: 'PH',
  209. PN: 'GB', // Pitcairn
  210. PL: 'PL',
  211. PT: 'PT',
  212. PR: 'US', // Puerto Rico
  213. QA: 'QA',
  214. RE: 'FR', // Reunion
  215. RO: 'RO',
  216. RU: 'RU',
  217. RW: 'RW',
  218. BL: 'FR', // Saint Barthelemy
  219. SH: 'GB', // Saint Helena
  220. KN: 'KN',
  221. LC: 'LC',
  222. MF: 'FR', // Saint Martin
  223. PM: 'FR', // Saint Pierre and Miquelon
  224. VC: 'VC',
  225. WS: 'WS',
  226. SM: 'SM',
  227. ST: 'ST',
  228. SA: 'SA',
  229. SN: 'SN',
  230. RS: 'RS',
  231. SC: 'SC',
  232. SL: 'SL',
  233. SG: 'SG',
  234. SX: 'NL', // Sint Maarten
  235. SK: 'SK',
  236. SI: 'SI',
  237. SB: 'SB',
  238. SO: 'SO',
  239. ZA: 'ZA',
  240. GS: 'GB', // South Georgia and the South Sandwich Islands
  241. ES: 'ES',
  242. LK: 'LK',
  243. SD: 'SD',
  244. SR: 'SR',
  245. SJ: 'NO', // Svalbard and Jan Mayen
  246. SZ: 'SZ',
  247. SE: 'SE',
  248. CH: 'CH',
  249. SY: 'SY',
  250. TW: 'TW', // Taiwan
  251. TJ: 'TJ',
  252. TZ: 'TZ',
  253. TH: 'TH',
  254. TL: 'TL',
  255. TG: 'TG',
  256. TK: 'NZ', // Tokelau
  257. TO: 'TO',
  258. TT: 'TT',
  259. TN: 'TN',
  260. TR: 'TR',
  261. TM: 'TM',
  262. TC: 'GB', // Turcs and Caicos Islands
  263. TV: 'TV',
  264. UG: 'UG',
  265. UA: 'UA',
  266. AE: 'AE',
  267. GB: 'GB',
  268. US: 'US',
  269. UM: 'US', // US Minor Outlying Islands
  270. UY: 'UY',
  271. UZ: 'UZ',
  272. VU: 'VU',
  273. VE: 'VE',
  274. VN: 'VN',
  275. VG: 'GB', // British Virgin Islands
  276. VI: 'US', // US Virgin Islands
  277. WF: 'FR', // Wallis and Futuna
  278. EH: 'MA', // Western Sahara
  279. YE: 'YE',
  280. ZM: 'ZM',
  281. ZW: 'ZW'
  282. };
  283.  
  284. if (AUTOMATIC && (API_Key.length <= 24 || API_Key.match("^[a-fA-F0-9]*$") == null)) {
  285. AUTOMATIC = false;
  286. };
  287.  
  288. function checkGameMode() {
  289. return (location.pathname.startsWith("/game/") || (ENABLED_ON_CHALLENGES && location.pathname.startsWith("/challenge/")));
  290. };
  291.  
  292. let _cndic = {};
  293. function cn(classNameStart) { // cn("status_section__") -> "status_section__8uP8o"
  294. let memorized = _cndic[classNameStart];
  295. if (memorized != null) return memorized;
  296. let selected = document.querySelector(`div[class*="${classNameStart}"]`);
  297. if (selected == null) return classNameStart;
  298. for (let className of selected.classList) {
  299. if (className.startsWith(classNameStart)) {
  300. _cndic[classNameStart] = className;
  301. return className;
  302. }
  303. }
  304. }
  305.  
  306. function geoguessrStyle(number) {
  307. return `<div class="${cn("guess-description-distance_distanceLabel__")}">
  308. <div class="${cn("styles_root__")} ${cn("styles_variantWhiteTransparent__")} ${cn("styles_roundnessSmall__")}">
  309. <div class="${cn("styles_start__")} ${cn("styles_right__")}"></div>
  310. <div class="${cn("guess-description-distance_distanceValue__")}">${number}</div>
  311. <div class="${cn("styles_end__")} ${cn("styles_right__")}"></div>
  312. </div>
  313. </div>`;
  314. };
  315.  
  316. function addCounter() {
  317. if (!checkGameMode()) {
  318. return;
  319. };
  320. let status_length = document.getElementsByClassName(cn("status_section__")).length;
  321. if (document.getElementById("country-streak") == null && status_length >= 3) {
  322. let position = (status_length >= 4 && document.getElementsByClassName(cn("status_label__"))[3].innerText == "TIME LEFT") ? 4 : 3;
  323. let newDiv0 = document.createElement("div");
  324. newDiv0.className = cn('status_section__');
  325. let statusBar = document.getElementsByClassName(cn("status_inner__"))[0];
  326. statusBar.insertBefore(newDiv0, statusBar.children[position]);
  327. newDiv0.innerHTML = `<div class="${cn("status_label__")}">Streak</div>
  328. <div id="country-streak" class="${cn("status_value__")}">${streak}</div>`;
  329. };
  330. };
  331.  
  332. function addStreakRoundResult() {
  333. if (document.getElementById("country-streak2") == null && !!document.querySelector('div[data-qa="guess-description"]')
  334. && !document.querySelector('div[class*="standard-final-result_section__"]')) {
  335. let pageProps = JSON.parse(document.getElementById("__NEXT_DATA__").innerHTML).props.pageProps;
  336. if (pageProps.gamePlayedByCurrentUser != null && pageProps.gamePlayedByCurrentUser.mode == "streak") return;
  337. let newDiv = document.createElement("div");
  338. document.querySelector('div[data-qa="guess-description"]').appendChild(newDiv);
  339. newDiv.innerHTML = `<div id="country-streak2" style="text-align:center;margin-top:10px;"><h2><i>Country Streak: ${streak}</i></h2></div>`;
  340. };
  341. };
  342.  
  343. function addStreakGameSummary() {
  344. if (document.getElementById("country-streak2") == null && !!document.querySelector('div[class*="standard-final-result_section__"]')) {
  345. let newDiv = document.createElement("div");
  346. let progressSection = document.getElementsByClassName(cn("standard-final-result_progressSection__"))[0];
  347. progressSection.parentNode.insertBefore(newDiv, progressSection.parentNode.children[2]);
  348. progressSection.style.marginTop = "10px";
  349. progressSection.style.marginBottom = "10px";
  350. newDiv.innerHTML = `<div id="country-streak2" style="text-align:center;margin-top:10px;"><h2><i>Country Streak: ${streak}</i></h2></div>`;
  351. };
  352. };
  353.  
  354. function updateStreak(newStreak) {
  355. if (newStreak === ERROR_RESP) {
  356. if (document.getElementById("country-streak2") != null && (!!document.querySelector('div[data-qa="guess-description"]'))) {
  357. document.getElementById("country-streak2").innerHTML =
  358. `<div><i>Country codes could not be fetched. If your API key is new, it should activate soon.</i></div>
  359. <div><i>Check for typos in the API key. You might also see this message if bigdatacloud is down</i></div>
  360. <div><i>or in the unlikely event that you have exceeded you quota limit of 50,000 requests.</i></div>
  361. <div><i>In the meantime, you can press 1 to count the country as correct, or press 0 otherwise.</i></div>`;
  362. }
  363. return;
  364. }
  365. sessionStorage.setItem("Streak", newStreak);
  366. if (!(streak > 0 && newStreak == 0)) {
  367. sessionStorage.setItem("StreakBackup", newStreak);
  368. };
  369. if (document.getElementById("country-streak") != null) {
  370. document.getElementById("country-streak").innerHTML = newStreak;
  371. };
  372. if (document.getElementById("country-streak2") != null
  373. && (!!document.querySelector('div[data-qa="guess-description"]') || !!document.querySelector('div[class*="standard-final-result_section__"]'))) {
  374. document.getElementById("country-streak2").innerHTML = `<h2><i>Country Streak: ${newStreak}</i></h2>`;
  375. if (newStreak == 0) {
  376. if (streak >= 2) {
  377. document.getElementById("country-streak2").innerHTML = `<h2><i>Country Streak: 0</i></h2>
  378. Your streak ended after correctly guessing ${geoguessrStyle(streak)} countries in a row.`;
  379. } else if (streak == 1) {
  380. document.getElementById("country-streak2").innerHTML = `<h2><i>Country Streak: 0</i></h2>
  381. Your streak ended after correctly guessing ${geoguessrStyle(1)} country.`;
  382. };
  383. };
  384. };
  385. streak = newStreak;
  386. };
  387.  
  388. async function getUserAsync(coords) {
  389. if (coords[0] <= -85.05) {
  390. return 'AQ';
  391. };
  392. let api = "https://api.bigdatacloud.net/data/reverse-geocode?latitude="+coords[0]+"&longitude="+coords[1]+"&localityLanguage=en&key="+API_Key
  393. let response = await fetch(api)
  394. .then(res => (res.status !== 200) ? ERROR_RESP : res.json())
  395. .then(out => (out === ERROR_RESP) ? ERROR_RESP : CountryDict[out.countryCode]);
  396. return response;
  397. };
  398.  
  399. function check() {
  400. const game_tag = window.location.href.substring(window.location.href.lastIndexOf('/') + 1)
  401. let api_url = ""
  402. if (location.pathname.startsWith("/game/")) {
  403. api_url = "https://www.geoguessr.com/api/v3/games/"+game_tag;
  404. } else if (location.pathname.startsWith("/challenge/")) {
  405. api_url = "https://www.geoguessr.com/api/v3/challenges/"+game_tag+"/game";
  406. };
  407. fetch(api_url)
  408. .then(res => res.json())
  409. .then((out) => {
  410. let guess_counter = out.player.guesses.length;
  411. let guess = [out.player.guesses[guess_counter-1].lat,out.player.guesses[guess_counter-1].lng];
  412. if (guess[0] == last_guess[0] && guess[1] == last_guess[1]) {
  413. return;
  414. };
  415. last_guess = guess;
  416. let round = [out.rounds[guess_counter-1].lat,out.rounds[guess_counter-1].lng];
  417. getUserAsync(guess)
  418. .then(gue => {
  419. getUserAsync(round)
  420. .then(loc => {
  421. if (loc == ERROR_RESP || gue == ERROR_RESP) {
  422. updateStreak(ERROR_RESP);
  423. } else if (loc == gue) {
  424. updateStreak(streak + 1);
  425. } else {
  426. updateStreak(0);
  427. };
  428. });
  429. });
  430. }).catch(err => { throw err });
  431. };
  432.  
  433. function doCheck() {
  434. if (!document.querySelector('div[class*="result-layout_root__"]')) {
  435. sessionStorage.setItem("Checked", 0);
  436. } else if (sessionStorage.getItem("Checked") == 0) {
  437. check();
  438. sessionStorage.setItem("Checked", 1);
  439. }
  440. };
  441.  
  442. function tryAddCounter() {
  443. addCounter();
  444. for (let timeout of [400,1200,2000,3000,4000]) {
  445. if (document.getElementsByClassName(cn("status_section__")).length == 0) {
  446. setTimeout(addCounter, timeout);
  447. };
  448. }
  449. };
  450.  
  451. function tryAddCounterOnRefresh() {
  452. setTimeout(addCounter, 50);
  453. setTimeout(addCounter, 300);
  454. };
  455.  
  456. function tryAddStreak() {
  457. if (!checkGameMode()) {
  458. return;
  459. };
  460. if (AUTOMATIC) {
  461. doCheck();
  462. for (let timeout of [250,500,1200,2000]) {
  463. setTimeout(doCheck, timeout);
  464. }
  465. };
  466. for (let timeout of [250,500,1200,2000]) {
  467. setTimeout(addStreakRoundResult, timeout);
  468. setTimeout(addStreakGameSummary, timeout);
  469. }
  470. };
  471.  
  472. document.addEventListener('keypress', (e) => {
  473. let streakBackup = parseInt(sessionStorage.getItem("StreakBackup"), 10);
  474. switch (e.key) {
  475. case '1':
  476. updateStreak(streak + 1);
  477. break;
  478. case '2':
  479. updateStreak(streak - 1);
  480. break;
  481. case '8':
  482. updateStreak(streakBackup + 1);
  483. break;
  484. case '0':
  485. updateStreak(0);
  486. sessionStorage.setItem("StreakBackup", 0);
  487. };
  488. });
  489.  
  490. document.addEventListener('click', tryAddCounter, false);
  491. document.addEventListener('click', tryAddStreak, false);
  492. document.addEventListener('keyup', (e) => { if (e.key === " ") { tryAddStreak(); } });
  493. document.addEventListener('load', tryAddCounterOnRefresh(), false);

QingJ © 2025

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