// ==UserScript== // @name Country Streak Counter // @version 1.3.4 // @description Adds a country streak counter to the GeoGuessr website // @match https://www.geoguessr.com/* // @author victheturtle#5159 // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com // @namespace https://greasyfork.org/users/967692-victheturtle // @downloadURL none // ==/UserScript== // Credits to subsymmetry for the original version of the Streak Counter const ENABLED_ON_CHALLENGES = false; //Replace with true or false const API_Key = 'ENTER_API_KEY_HERE'; //Replace ENTER_API_KEY_HERE with your API key (so keep the quote marks) let AUTOMATIC = true; //Replace with false for a manual counter. Without an API key, the counter will still be manual if (sessionStorage.getItem("Streak") == null) { sessionStorage.setItem("Streak", 0); }; if (sessionStorage.getItem("StreakBackup") == null) { sessionStorage.setItem("StreakBackup", 0); }; if (sessionStorage.getItem("Checked") == null) { sessionStorage.setItem("Checked", 0); }; let streak = parseInt(sessionStorage.getItem("Streak"), 10); const ERROR_RESP = -1000000; var CountryDict = { AF: 'AF', AX: 'FI', // Aland Islands AL: 'AL', DZ: 'DZ', AS: 'US', // American Samoa AD: 'AD', AO: 'AO', AI: 'GB', // Anguilla AQ: 'AQ', // Antarctica AG: 'AG', AR: 'AR', AM: 'AM', AW: 'NL', // Aruba AU: 'AU', AT: 'AT', AZ: 'AZ', BS: 'BS', BH: 'BH', BD: 'BD', BB: 'BB', BY: 'BY', BE: 'BE', BZ: 'BZ', BJ: 'BJ', BM: 'GB', // Bermuda BT: 'BT', BO: 'BO', BQ: 'NL', // Bonaire, Sint Eustatius, Saba BA: 'BA', BW: 'BW', BV: 'NO', // Bouvet Island BR: 'BR', IO: 'GB', // British Indian Ocean Territory BN: 'BN', BG: 'BG', BF: 'BF', BI: 'BI', KH: 'KH', CM: 'CM', CA: 'CA', CV: 'CV', KY: 'UK', // Cayman Islands CF: 'CF', TD: 'TD', CL: 'CL', CN: 'CN', CX: 'AU', // Christmas Islands CC: 'AU', // Cocos (Keeling) Islands CO: 'CO', KM: 'KM', CG: 'CG', CD: 'CD', CK: 'NZ', // Cook Islands CR: 'CR', CI: 'CI', HR: 'HR', CU: 'CU', CW: 'NL', // Curacao CY: 'CY', CZ: 'CZ', DK: 'DK', DJ: 'DJ', DM: 'DM', DO: 'DO', EC: 'EC', EG: 'EG', SV: 'SV', GQ: 'GQ', ER: 'ER', EE: 'EE', ET: 'ET', FK: 'GB', // Falkland Islands FO: 'DK', // Faroe Islands FJ: 'FJ', FI: 'FI', FR: 'FR', GF: 'FR', // French Guiana PF: 'FR', // French Polynesia TF: 'FR', // French Southern Territories GA: 'GA', GM: 'GM', GE: 'GE', DE: 'DE', GH: 'GH', GI: 'UK', // Gibraltar GR: 'GR', GL: 'DK', // Greenland GD: 'GD', GP: 'FR', // Guadeloupe GU: 'US', // Guam GT: 'GT', GG: 'GB', // Guernsey GN: 'GN', GW: 'GW', GY: 'GY', HT: 'HT', HM: 'AU', // Heard Island and McDonald Islands VA: 'VA', HN: 'HN', HK: 'CN', // Hong Kong HU: 'HU', IS: 'IS', IN: 'IN', ID: 'ID', IR: 'IR', IQ: 'IQ', IE: 'IE', IM: 'GB', // Isle of Man IL: 'IL', IT: 'IT', JM: 'JM', JP: 'JP', JE: 'GB', // Jersey JO: 'JO', KZ: 'KZ', KE: 'KE', KI: 'KI', KR: 'KR', KW: 'KW', KG: 'KG', LA: 'LA', LV: 'LV', LB: 'LB', LS: 'LS', LR: 'LR', LY: 'LY', LI: 'LI', LT: 'LT', LU: 'LU', MO: 'CN', // Macao MK: 'MK', MG: 'MG', MW: 'MW', MY: 'MY', MV: 'MV', ML: 'ML', MT: 'MT', MH: 'MH', MQ: 'FR', // Martinique MR: 'MR', MU: 'MU', YT: 'FR', // Mayotte MX: 'MX', FM: 'FM', MD: 'MD', MC: 'MC', MN: 'MN', ME: 'ME', MS: 'GB', // Montserrat MA: 'MA', MZ: 'MZ', MM: 'MM', NA: 'NA', NR: 'NR', NP: 'NP', NL: 'NL', AN: 'NL', // Netherlands Antilles NC: 'FR', // New Caledonia NZ: 'NZ', NI: 'NI', NE: 'NE', NG: 'NG', NU: 'NZ', // Niue NF: 'AU', // Norfolk Island MP: 'US', // Northern Mariana Islands NO: 'NO', OM: 'OM', PK: 'PK', PW: 'PW', PS: 'IL', // Palestine PA: 'PA', PG: 'PG', PY: 'PY', PE: 'PE', PH: 'PH', PN: 'GB', // Pitcairn PL: 'PL', PT: 'PT', PR: 'US', // Puerto Rico QA: 'QA', RE: 'FR', // Reunion RO: 'RO', RU: 'RU', RW: 'RW', BL: 'FR', // Saint Barthelemy SH: 'GB', // Saint Helena KN: 'KN', LC: 'LC', MF: 'FR', // Saint Martin PM: 'FR', // Saint Pierre and Miquelon VC: 'VC', WS: 'WS', SM: 'SM', ST: 'ST', SA: 'SA', SN: 'SN', RS: 'RS', SC: 'SC', SL: 'SL', SG: 'SG', SX: 'NL', // Sint Maarten SK: 'SK', SI: 'SI', SB: 'SB', SO: 'SO', ZA: 'ZA', GS: 'GB', // South Georgia and the South Sandwich Islands ES: 'ES', LK: 'LK', SD: 'SD', SR: 'SR', SJ: 'NO', // Svalbard and Jan Mayen SZ: 'SZ', SE: 'SE', CH: 'CH', SY: 'SY', TW: 'TW', // Taiwan TJ: 'TJ', TZ: 'TZ', TH: 'TH', TL: 'TL', TG: 'TG', TK: 'NZ', // Tokelau TO: 'TO', TT: 'TT', TN: 'TN', TR: 'TR', TM: 'TM', TC: 'GB', // Turcs and Caicos Islands TV: 'TV', UG: 'UG', UA: 'UA', AE: 'AE', GB: 'GB', US: 'US', UM: 'US', // US Minor Outlying Islands UY: 'UY', UZ: 'UZ', VU: 'VU', VE: 'VE', VN: 'VN', VG: 'GB', // British Virgin Islands VI: 'US', // US Virgin Islands WF: 'FR', // Wallis and Futuna EH: 'MA', // Western Sahara YE: 'YE', ZM: 'ZM', ZW: 'ZW' }; if (AUTOMATIC && (API_Key.length <= 24 || API_Key.match("^[a-fA-F0-9_]*$") == null)) { AUTOMATIC = false; }; function checkGameMode() { return (location.pathname.startsWith("/game/") || (ENABLED_ON_CHALLENGES && location.pathname.startsWith("/challenge/"))); }; let _cndic = {}; function cn(classNameStart) { // cn("status_section__") -> "status_section__8uP8o" let memorized = _cndic[classNameStart]; if (memorized != null) return memorized; let selected = document.querySelector(`div[class*="${classNameStart}"]`); if (selected == null) return classNameStart; for (let className of selected.classList) { if (className.startsWith(classNameStart)) { _cndic[classNameStart] = className; return className; } } } function geoguessrStyle(number) { return `
${number}
`; }; function addStreakStatusBar() { let status_length = document.getElementsByClassName(cn("status_section__")).length; if (document.getElementById("country-streak") == null && status_length >= 3) { let newDiv = document.createElement("div"); newDiv.className = cn('status_section__'); newDiv.innerHTML = `
Streak
${streak}
`; let statusBar = document.getElementsByClassName(cn("status_inner__"))[0]; statusBar.insertBefore(newDiv, statusBar.children[3]); }; }; function addStreakRoundResult() { if (document.getElementById("country-streak2") == null && !!document.querySelector('div[data-qa="guess-description"]') && !document.querySelector('div[class*="standard-final-result_section__"]')) { let newDiv = document.createElement("div"); newDiv.innerHTML = `

Country Streak: ${streak}

`; document.querySelector('div[data-qa="guess-description"]').appendChild(newDiv); }; }; function addStreakGameSummary() { if (document.getElementById("country-streak2") == null && !!document.querySelector('div[class*="standard-final-result_section__"]')) { let newDiv = document.createElement("div"); newDiv.innerHTML = `

Country Streak: ${streak}

`; let progressSection = document.querySelector('div[class*="standard-final-result_progressSection__"]'); progressSection.parentNode.insertBefore(newDiv, progressSection.parentNode.children[2]); progressSection.style.marginTop = "10px"; progressSection.style.marginBottom = "10px"; }; }; function updateStreak(newStreak) { geoguessrStyle() // call cn() for the geoguessrStyle styles to memorize them while they are there if (newStreak === ERROR_RESP) { if (document.getElementById("country-streak2") != null && (!!document.querySelector('div[data-qa="guess-description"]'))) { document.getElementById("country-streak2").innerHTML = `
Country codes could not be fetched. If your API key is new, it should activate soon.
Check for typos in the API key. You might also see this message if bigdatacloud is down
or in the unlikely event that you have exceeded you quota limit of 50,000 requests.
In the meantime, you can press 1 to count the country as correct, or press 0 otherwise.
`; } return; } sessionStorage.setItem("Streak", newStreak); if (!(streak > 0 && newStreak == 0)) { sessionStorage.setItem("StreakBackup", newStreak); }; if (document.getElementById("country-streak") != null) { document.getElementById("country-streak").innerHTML = newStreak; }; if (document.getElementById("country-streak2") != null) { document.getElementById("country-streak2").innerHTML = `

Country Streak: ${newStreak}

`; if (newStreak == 0) { if (streak >= 2) { document.getElementById("country-streak2").innerHTML = `

Country Streak: 0

Your streak ended after correctly guessing ${geoguessrStyle(streak)} countries in a row.`; } else if (streak == 1) { document.getElementById("country-streak2").innerHTML = `

Country Streak: 0

Your streak ended after correctly guessing ${geoguessrStyle(1)} country.`; }; }; }; streak = newStreak; }; async function getUserAsync(coords) { if (coords[0] <= -85.05) { return 'AQ'; }; const api = "https://api.bigdatacloud.net/data/reverse-geocode?latitude="+coords[0]+"&longitude="+coords[1]+"&localityLanguage=en&key="+API_Key const response = await fetch(api) .then(res => (res.status !== 200) ? ERROR_RESP : res.json()) .then(out => (out === ERROR_RESP) ? ERROR_RESP : CountryDict[out.countryCode]); return response; }; let lastGuess = [0,0]; function check() { const gameTag = window.location.href.substring(window.location.href.lastIndexOf('/') + 1) let apiUrl = "" if (location.pathname.startsWith("/game/")) { apiUrl = "https://www.geoguessr.com/api/v3/games/"+gameTag; } else if (location.pathname.startsWith("/challenge/")) { apiUrl = "https://www.geoguessr.com/api/v3/challenges/"+gameTag+"/game"; }; fetch(apiUrl) .then(res => res.json()) .then((out) => { const guessCounter = out.player.guesses.length; const guess = [out.player.guesses[guessCounter-1].lat,out.player.guesses[guessCounter-1].lng]; if (guess[0] == lastGuess[0] && guess[1] == lastGuess[1]) { return; }; lastGuess = guess; const round = [out.rounds[guessCounter-1].lat,out.rounds[guessCounter-1].lng]; getUserAsync(guess) .then(gue => { getUserAsync(round) .then(loc => { if (loc == ERROR_RESP || gue == ERROR_RESP) { updateStreak(ERROR_RESP); } else if (loc == gue) { updateStreak(streak + 1); } else { updateStreak(0); }; }); }); }).catch(err => { throw err }); }; let lastDoCheckCall = 0 function doCheck() { if (lastDoCheckCall >= (Date.now() - 200)) return; lastDoCheckCall = Date.now(); if (!document.querySelector('div[class*="result-layout_root__"]')) { sessionStorage.setItem("Checked", 0); } else if (sessionStorage.getItem("Checked") == 0) { check(); sessionStorage.setItem("Checked", 1); } }; let observer = new MutationObserver((mutations) => { if (!checkGameMode()) return; if (AUTOMATIC) doCheck(); addStreakStatusBar(); addStreakRoundResult(); addStreakGameSummary(); }); observer.observe(document.body, { subtree: true, childList: true }); document.addEventListener('keypress', (e) => { let streakBackup = parseInt(sessionStorage.getItem("StreakBackup"), 10); switch (e.key) { case '1': updateStreak(streak + 1); break; case '2': updateStreak(streak - 1); break; case '8': updateStreak(streakBackup + 1); break; case '0': updateStreak(0); sessionStorage.setItem("StreakBackup", 0); }; });