// ==UserScript==
// @name Country Streak Counter
// @version 1.3.3
// @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
let ENABLED_ON_CHALLENGES = false; //Replace with true or false
let 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);
let last_guess = [0,0];
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 `
`;
};
function addCounter() {
if (!checkGameMode()) {
return;
};
let status_length = document.getElementsByClassName(cn("status_section__")).length;
if (document.getElementById("country-streak") == null && status_length >= 3) {
let position = (status_length >= 4 && document.getElementsByClassName(cn("status_label__"))[3].innerText == "TIME LEFT") ? 4 : 3;
let newDiv0 = document.createElement("div");
newDiv0.className = cn('status_section__');
let statusBar = document.getElementsByClassName(cn("status_inner__"))[0];
statusBar.insertBefore(newDiv0, statusBar.children[position]);
newDiv0.innerHTML = `Streak
${streak}
`;
};
};
function addStreakRoundResult() {
if (document.getElementById("country-streak2") == null && !!document.querySelector('div[data-qa="guess-description"]')
&& !document.querySelector('div[class*="standard-final-result_section__"]')) {
let pageProps = JSON.parse(document.getElementById("__NEXT_DATA__").innerHTML).props.pageProps;
if (pageProps.gamePlayedByCurrentUser != null && pageProps.gamePlayedByCurrentUser.mode == "streak") return;
let newDiv = document.createElement("div");
document.querySelector('div[data-qa="guess-description"]').appendChild(newDiv);
newDiv.innerHTML = `Country Streak: ${streak}
`;
};
};
function addStreakGameSummary() {
if (document.getElementById("country-streak2") == null && !!document.querySelector('div[class*="standard-final-result_section__"]')) {
let newDiv = document.createElement("div");
let progressSection = document.getElementsByClassName(cn("standard-final-result_progressSection__"))[0];
progressSection.parentNode.insertBefore(newDiv, progressSection.parentNode.children[2]);
progressSection.style.marginTop = "10px";
progressSection.style.marginBottom = "10px";
newDiv.innerHTML = `Country Streak: ${streak}
`;
};
};
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.querySelector('div[data-qa="guess-description"]') || !!document.querySelector('div[class*="standard-final-result_section__"]'))) {
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';
};
let api = "https://api.bigdatacloud.net/data/reverse-geocode?latitude="+coords[0]+"&longitude="+coords[1]+"&localityLanguage=en&key="+API_Key
let 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;
};
function check() {
const game_tag = window.location.href.substring(window.location.href.lastIndexOf('/') + 1)
let api_url = ""
if (location.pathname.startsWith("/game/")) {
api_url = "https://www.geoguessr.com/api/v3/games/"+game_tag;
} else if (location.pathname.startsWith("/challenge/")) {
api_url = "https://www.geoguessr.com/api/v3/challenges/"+game_tag+"/game";
};
fetch(api_url)
.then(res => res.json())
.then((out) => {
let guess_counter = out.player.guesses.length;
let guess = [out.player.guesses[guess_counter-1].lat,out.player.guesses[guess_counter-1].lng];
if (guess[0] == last_guess[0] && guess[1] == last_guess[1]) {
return;
};
last_guess = guess;
let round = [out.rounds[guess_counter-1].lat,out.rounds[guess_counter-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 });
};
function doCheck() {
if (!document.querySelector('div[class*="result-layout_root__"]')) {
sessionStorage.setItem("Checked", 0);
} else if (sessionStorage.getItem("Checked") == 0) {
check();
sessionStorage.setItem("Checked", 1);
}
};
function tryAddCounter() {
addCounter();
for (let timeout of [400,1200,2000,3000,4000]) {
if (document.getElementsByClassName(cn("status_section__")).length == 0) {
setTimeout(addCounter, timeout);
};
}
};
function tryAddCounterOnRefresh() {
setTimeout(addCounter, 50);
setTimeout(addCounter, 300);
};
function tryAddStreak() {
if (!checkGameMode()) {
return;
};
if (AUTOMATIC) {
doCheck();
for (let timeout of [250,500,1200,2000]) {
setTimeout(doCheck, timeout);
}
};
for (let timeout of [250,500,1200,2000]) {
setTimeout(addStreakRoundResult, timeout);
setTimeout(addStreakGameSummary, timeout);
}
};
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);
};
});
document.addEventListener('click', tryAddCounter, false);
document.addEventListener('click', tryAddStreak, false);
document.addEventListener('keyup', (e) => { if (e.key === " ") { tryAddStreak(); } });
document.addEventListener('load', tryAddCounterOnRefresh(), false);