Skip to content

Instantly share code, notes, and snippets.

@Aracturat
Last active May 20, 2025 20:03
Show Gist options
  • Save Aracturat/e39914220b418ccbd60c5b53e49a2461 to your computer and use it in GitHub Desktop.
Save Aracturat/e39914220b418ccbd60c5b53e49a2461 to your computer and use it in GitHub Desktop.
geostats-ranked-duels.js
//Get User ID
let USER_ID = JSON.parse(document.getElementById("__NEXT_DATA__").innerText)
.props.accountProps.account.user.userId;
const getMyGames = (paginationToken) => {
return fetch(
`https://www.geoguessr.com/api/v4/feed/private?count=26&paginationToken=${paginationToken}`,
{
headers: {
"content-type": "application/json",
},
method: "GET",
credentials: "include",
}
).then((e) => e.json());
};
const getMyLastGames = async (count) => {
let paginationToken = "";
const games = [];
while (true) {
const { entries: newGames, paginationToken: newPaginationToken } =
await getMyGames(paginationToken);
const subGames = newGames
.flatMap((game) => {
if (game.payload) {
return JSON.parse(game.payload);
} else {
return null;
}
})
.filter((e) => e?.payload?.gameMode === "Duels")
.map((e) => e?.payload?.gameId);
games.push(...subGames);
paginationToken = newPaginationToken;
if (games.length >= count || !newPaginationToken) {
break;
}
}
return games;
};
//Evaluate the new duels guess
function evaluateDuelsGuess(
guess,
location,
distance,
userScore,
opponentScore,
startTime,
timerTime,
guessTime,
opponentDistance
) {
if (guess == "AQ") {
return;
}
let correct_exists = window.localStorage.getItem(
`${location}-number-correct-${USER_ID}`
);
let total_exists = window.localStorage.getItem(
`${location}-number-total-${USER_ID}`
);
let correct_overall = window.localStorage.getItem(
`overall-correct-${USER_ID}`
);
let total_overall = window.localStorage.getItem(`overall-total-${USER_ID}`);
let correct_value = 1;
let total_value = 1;
//Setting correct value
if (guess === location) {
if (correct_exists !== null) {
correct_value = parseInt(correct_exists, 10);
correct_value = correct_value + 1;
}
window.localStorage.setItem(
`${location}-number-correct-${USER_ID}`,
correct_value
);
}
//Setting overall values
if (total_overall !== null) {
if (guess === location) {
window.localStorage.setItem(
`overall-correct-${USER_ID}`,
parseInt(correct_overall, 10) + 1
);
}
window.localStorage.setItem(
`overall-total-${USER_ID}`,
parseInt(total_overall, 10) + 1
);
} else {
if (guess === location) {
window.localStorage.setItem(`overall-correct-${USER_ID}`, 1);
} else {
window.localStorage.setItem(`overall-correct-${USER_ID}`, 0);
}
window.localStorage.setItem(`overall-total-${USER_ID}`, 1);
}
//Setting total value
if (total_exists !== null) {
total_value = parseInt(total_exists, 10);
total_value = total_value + 1;
}
window.localStorage.setItem(
`${location}-number-total-${USER_ID}`,
total_value
);
//Setting distance
let distance_average = window.localStorage.getItem(
`${location}-distance-average-${USER_ID}`
);
let distance_number = window.localStorage.getItem(
`${location}-distance-number-${USER_ID}`
);
if (distance_average === null && distance_number === null) {
window.localStorage.setItem(
`${location}-distance-average-${USER_ID}`,
distance
);
window.localStorage.setItem(`${location}-distance-number-${USER_ID}`, 1);
} else {
distance_number = parseInt(distance_number) + 1;
distance_average =
(distance_average * (distance_number - 1) + distance) / distance_number;
window.localStorage.setItem(
`${location}-distance-average-${USER_ID}`,
distance_average
);
window.localStorage.setItem(
`${location}-distance-number-${USER_ID}`,
distance_number
);
}
//SETTING ALL OTHER STATS FOR COUNTRY AND OVERALL
let countryStats = {
totalCount: 0,
totalCorrect: 0,
averageScore: 0,
averageDistance: 0,
closerThanOpponent: 0,
averageTime: 0,
guessedFirst: 0,
total5ks: 0,
countryCorrectStreak: 0,
countryCorrectMax: 0,
countryWrongStreak: 0,
countryWrongMax: 0,
distanceFromOpponent: [0, 0],
//Wrong country
incorrectCountry: {},
};
let overallStats = {
//Total
totalCount: 0,
totalCorrect: 0,
totalWin: 0,
totalLoss: 0,
total5ks: 0,
totalGames: 0,
totalSeconds: 0,
//Average
averageScore: 0,
averageDistance: 0,
averageGameLengthWin: 0,
averageGameLengthLoss: 0,
averageTime: 0,
//Wrong country
incorrectCountry: {},
//Other
guessedFirst: 0,
closerThanOpponent: 0,
countryCorrectStreak: 0,
countryCorrectMax: 0,
countryWrongStreak: 0,
countryWrongMax: 0,
opponentRating: 0,
distanceFromOpponent: [0, 0],
};
if (window.localStorage.getItem(`${location}-all-country-stats-${USER_ID}`)) {
countryStats = JSON.parse(
window.localStorage.getItem(`${location}-all-country-stats-${USER_ID}`)
);
}
if (window.localStorage.getItem(`overall-country-stats-${USER_ID}`)) {
overallStats = JSON.parse(
window.localStorage.getItem(`overall-country-stats-${USER_ID}`)
);
}
//Time String to Seconds
function timeToSeconds(value) {
let time = value.slice(11, 19);
time = time.split(/[.:]/);
time =
parseInt(time[0]) * 60 * 60 + parseInt(time[1]) * 60 + parseInt(time[2]);
return time;
}
//Get Correct Time Values
startTime = timeToSeconds(startTime);
timerTime = timeToSeconds(timerTime);
guessTime = timeToSeconds(guessTime);
let timeToGuess = guessTime - startTime;
//Total Count
countryStats.totalCount++;
overallStats.totalCount++;
//Total Correct
countryStats.totalCorrect = correct_value;
overallStats.totalCorrect = parseInt(correct_overall, 10);
//Average Score
countryStats.averageScore =
(countryStats.averageScore * (countryStats.totalCount - 1) + userScore) /
countryStats.totalCount;
overallStats.averageScore =
(overallStats.averageScore * (overallStats.totalCount - 1) + userScore) /
overallStats.totalCount;
//Average Distance
countryStats.averageDistance =
(countryStats.averageDistance * (countryStats.totalCount - 1) + distance) /
countryStats.totalCount;
overallStats.averageDistance =
(overallStats.averageDistance * (overallStats.totalCount - 1) + distance) /
overallStats.totalCount;
//Average Time
if (timeToGuess >= 0) {
countryStats.averageTime =
(countryStats.averageTime * (countryStats.totalCount - 1) + timeToGuess) /
countryStats.totalCount;
overallStats.averageTime =
(overallStats.averageTime * (overallStats.totalCount - 1) + timeToGuess) /
overallStats.totalCount;
}
//Closer Than Opponent
if (userScore >= opponentScore) {
countryStats.closerThanOpponent++;
overallStats.closerThanOpponent++;
}
//Guessed First
if (guessTime == timerTime) {
countryStats.guessedFirst++;
overallStats.guessedFirst++;
}
//Total 5ks
if (userScore == 5000) {
countryStats.total5ks++;
overallStats.total5ks++;
}
//Country Streaks (Correct)
if (guess == location) {
//Country
countryStats.countryWrongStreak = 0;
countryStats.countryCorrectStreak++;
if (countryStats.countryCorrectStreak > countryStats.countryCorrectMax) {
countryStats.countryCorrectMax = countryStats.countryCorrectStreak;
}
//Overall
overallStats.countryWrongStreak = 0;
overallStats.countryCorrectStreak++;
if (overallStats.countryCorrectStreak > overallStats.countryCorrectMax) {
overallStats.countryCorrectMax = overallStats.countryCorrectStreak;
}
}
//Country Streaks (Incorrect)
if (guess != location) {
//Country
countryStats.countryCorrectStreak = 0;
countryStats.countryWrongStreak++;
if (countryStats.countryWrongStreak > countryStats.countryWrongMax) {
countryStats.countryWrongMax = countryStats.countryWrongStreak;
}
//Overall
overallStats.countryCorrectStreak = 0;
overallStats.countryWrongStreak++;
if (overallStats.countryWrongStreak > overallStats.countryWrongMax) {
overallStats.countryWrongMax = overallStats.countryWrongStreak;
}
}
//Wrong Country
if (guess != location) {
//Overall
if (!overallStats.incorrectCountry) {
overallStats.incorrectCountry = {};
}
if (location != "ERROR") {
if (overallStats.incorrectCountry[`${location}`]) {
overallStats.incorrectCountry[`${location}`]++;
} else {
overallStats.incorrectCountry[`${location}`] = 1;
}
}
//Country
if (!countryStats.incorrectCountry) {
countryStats.incorrectCountry = {};
}
if (guess != "ERROR") {
if (countryStats.incorrectCountry[`${guess}`]) {
countryStats.incorrectCountry[`${guess}`]++;
} else {
countryStats.incorrectCountry[`${guess}`] = 1;
}
}
}
//Distance From Opponent
if (opponentDistance != "DNF") {
let distanceDifference = opponentDistance - distance;
//Country
if (countryStats.distanceFromOpponent) {
countryStats.distanceFromOpponent[0]++;
countryStats.distanceFromOpponent[1] =
(countryStats.distanceFromOpponent[1] *
(countryStats.distanceFromOpponent[0] - 1) +
distanceDifference) /
countryStats.distanceFromOpponent[0];
} else {
countryStats.distanceFromOpponent = [1, distanceDifference];
}
//Overall
if (overallStats.distanceFromOpponent) {
overallStats.distanceFromOpponent[0]++;
overallStats.distanceFromOpponent[1] =
(overallStats.distanceFromOpponent[1] *
(overallStats.distanceFromOpponent[0] - 1) +
distanceDifference) /
overallStats.distanceFromOpponent[0];
} else {
overallStats.distanceFromOpponent = [1, distanceDifference];
}
}
//Save Updated Stats
window.localStorage.setItem(
`${location}-all-country-stats-${USER_ID}`,
JSON.stringify(countryStats)
);
window.localStorage.setItem(
`overall-country-stats-${USER_ID}`,
JSON.stringify(overallStats)
);
}
//Time String to Seconds
function timeToSeconds(value) {
let time = value.slice(11, 19);
time = time.split(/[.:]/);
time =
parseInt(time[0]) * 60 * 60 + parseInt(time[1]) * 60 + parseInt(time[2]);
return time;
}
//Add Stats When Duels Game Is Finished
function duelsGameFinished(
userHealth,
opponentHealth,
gameLength,
start,
end
) {
let overallStats = JSON.parse(
window.localStorage.getItem(`overall-country-stats-${USER_ID}`)
);
let gameTime = timeToSeconds(end) - timeToSeconds(start);
//If there is a calculation error (end is smaller than start)
if (gameTime < 0) {
gameTime = 0;
}
if (overallStats.totalSeconds < 0) {
overallStats.totalSeconds = 0;
}
//Total Games Played
overallStats.totalGames++;
//Win or Loss
let win = false;
if (userHealth > opponentHealth) {
win = true;
}
//Game Length (Win)
if (win) {
overallStats.totalWin++;
overallStats.averageGameLengthWin =
(overallStats.averageGameLengthWin * (overallStats.totalWin - 1) +
gameLength) /
overallStats.totalWin;
}
//Game Length (Loss)
if (!win) {
overallStats.totalLoss++;
overallStats.averageGameLengthLoss =
(overallStats.averageGameLengthLoss * (overallStats.totalLoss - 1) +
gameLength) /
overallStats.totalLoss;
}
//Total Seconds Played
if (overallStats.totalSeconds) {
overallStats.totalSeconds += gameTime;
} else {
overallStats.totalSeconds = gameTime;
}
//Save Updated Stats
window.localStorage.setItem(
`overall-country-stats-${USER_ID}`,
JSON.stringify(overallStats)
);
}
//Check the country code
function checkGuessCountryCode(out) {
let countryCode = "NO CODE";
if (out.address.country_code) {
countryCode = out.address.country_code;
}
//Check for US territories
if (countryCode == "us") {
//Puerto Rico
if (out.address["ISO3166-2-lvl4"] == "US-PR") {
countryCode = "pr";
}
//Guam
if (out.address["ISO3166-2-lvl4"] == "US-GU") {
countryCode = "gu";
}
//Northern Mariana Islands
if (out.address["ISO3166-2-lvl4"] == "US-MP") {
countryCode = "mp";
}
//Virgin Islands
if (out.address["ISO3166-2-lvl4"] == "US-VI") {
countryCode = "vi";
}
//American Samoa
if (out.address["ISO3166-2-lvl4"] == "US-AS") {
countryCode = "as";
}
}
//US Minor Outlying Islands
if (countryCode == "um") {
countryCode = "us";
}
//Check for AU territories
if (countryCode == "au") {
//Cocos Islands
if (out.address["territory"] == "Cocos (Keeling) Islands") {
countryCode = "cc";
}
//Christmas Island
if (out.address["territory"] == "Christmas Island") {
countryCode = "cx";
}
}
//Check for NL territories
if (countryCode == "nl") {
//Curaçao
if (out.address["ISO3166-2-lvl3"] == "NL-CW") {
countryCode = "cw";
}
}
//Check for Palestine
if (countryCode == "ps") {
countryCode = "il";
}
//Check for Hong Kong & Macau
if (countryCode == "cn") {
if (out.address["ISO3166-2-lvl3"]) {
if (out.address["ISO3166-2-lvl3"] == "CN-HK") {
countryCode = "hk";
} else if (out.address["ISO3166-2-lvl3"] == "CN-MO") {
countryCode = "mo";
}
}
}
return countryCode;
}
//Get the guess country code
async function getGuessCountryCode(location) {
if (location[0] <= -85.05 || location == null) {
return "AQ";
} else {
let api =
"https://nominatim.openstreetmap.org/reverse.php?lat=" +
location[0] +
"&lon=" +
location[1] +
"&format=jsonv2";
let country_code = await fetch(api)
.then((res) => res.json())
.then((out) => {
return checkGuessCountryCode(out);
})
.catch((err) => {
return "ERROR";
});
return country_code.toUpperCase();
}
}
//Check if a new duels guess has been made
async function checkDuelsGuess(gameId) {
let parsedGames = JSON.parse(
window.localStorage.getItem("parsed-game-ids") ?? "[]"
);
if (!parsedGames.length) {
window.localStorage.setItem("parsed-game-ids", "[]");
}
if (parsedGames.includes(gameId)) {
return;
}
const api = `https://game-server.geoguessr.com/api/duels/${gameId}`;
const out = await fetch(api, { credentials: "include" }).then((res) =>
res.json()
);
parsedGames.push(gameId);
window.localStorage.setItem("parsed-game-ids", JSON.stringify(parsedGames));
let player_index;
if (out.teams[0].players[0].playerId === USER_ID) {
player_index = 0;
} else if (out.teams[1].players[0].playerId === USER_ID) {
player_index = 1;
} else {
return;
}
for (
let guess_index = 1;
guess_index <= out.teams[player_index].players[0].guesses.length;
guess_index++
) {
let current_guess = [
out.teams[player_index].players[0].guesses[guess_index - 1].lat,
out.teams[player_index].players[0].guesses[guess_index - 1].lng,
];
await getGuessCountryCode(current_guess).then((guess) => {
//Get all values
let location_code =
out.rounds[guess_index - 1].panorama.countryCode.toUpperCase();
let distance =
out.teams[player_index].players[0].guesses[guess_index - 1].distance;
let opponent_index = 0;
if (player_index == 0) {
opponent_index = 1;
}
let userScore =
out.teams[player_index].roundResults[guess_index - 1].score;
let opponentScore =
out.teams[opponent_index].roundResults[guess_index - 1].score;
let startTime = out.rounds[guess_index - 1].startTime;
let timerTime = out.rounds[guess_index - 1].timerStartTime;
let guessTime =
out.teams[player_index].players[0].guesses[guess_index - 1].created;
let opponentDistance = "DNF";
let opponentGuess = out.teams[opponent_index].players[0];
if (
opponentGuess.guesses[opponentGuess.guesses.length - 1] &&
opponentGuess.guesses[opponentGuess.guesses.length - 1].roundNumber ==
guess_index
) {
opponentDistance =
opponentGuess.guesses[opponentGuess.guesses.length - 1].distance;
}
//If guess is good
if (guess != undefined) {
//Check location code
location_code = checkGuessCountryCode({
address: { country_code: location_code },
});
//Evaluate all values and update stats
evaluateDuelsGuess(
guess,
location_code,
distance,
userScore,
opponentScore,
startTime,
timerTime,
guessTime,
opponentDistance
);
}
});
}
//If the game is finished
if (out.status == "Finished") {
let opponentIndex = 0;
if (player_index == 0) {
opponentIndex = 1;
}
let userHealth = out.teams[player_index].health;
let opponentHealth = out.teams[opponentIndex].health;
let gameLength = out.currentRoundNumber;
let gameStartTime = out.rounds[0].startTime;
let gameEndTime = out.rounds[gameLength - 1].endTime;
duelsGameFinished(
userHealth,
opponentHealth,
gameLength,
gameStartTime,
gameEndTime
);
}
}
const getMyResults = async (count) => {
const myDuels = await getMyLastGames(count);
for (let i = 0; i < myDuels.length; i++) {
console.log(`Evaluate game ${i + 1}/${myDuels.length}`)
const gameId = myDuels[i];
await checkDuelsGuess(gameId);
}
};
await getMyResults(1000)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment