Country Streak Counter

Adds a country streak counter to the GeoGuessr website

目前为 2022-10-12 提交的版本。查看 最新版本

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

QingJ © 2025

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