Country Streak Counter

Adds a country streak counter to the GeoGuessr website

目前為 2023-08-28 提交的版本,檢視 最新版本

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

QingJ © 2025

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