Skip to content

Instantly share code, notes, and snippets.

@Aracturat
Last active May 20, 2025 08:03
Show Gist options
  • Save Aracturat/48d9ae6aae487c50046207228962046e to your computer and use it in GitHub Desktop.
Save Aracturat/48d9ae6aae487c50046207228962046e to your computer and use it in GitHub Desktop.
Geoguessr statistics
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;
}
})
.map((e) => e?.payload?.gameId)
.filter(Boolean);
games.push(...subGames);
paginationToken = newPaginationToken;
if (games.length >= count || !newPaginationToken) {
break;
}
}
return games;
};
const getGameResults = (gameId) => {
return fetch(
`https://www.geoguessr.com/_next/data/${window.__NEXT_DATA__.buildId}/en/duels/${gameId}/replay.json`,
{
method: "GET",
credentials: "include",
}
).then((e) => e.json()).catch(() => ({ pageProps: { statusCode: 404 }}));
};
const getPlayerResults = async (gameId, playerId) => {
const { pageProps } = await getGameResults(gameId);
if (pageProps.statusCode !== 200) {
return [];
}
const gameMode = pageProps.game.options.movementOptions.forbidZooming
? "NMPZ"
: pageProps.game.options.movementOptions.forbidMoving
? "NO MOVE"
: "MOVE";
const countryCodes = pageProps.game.rounds.map(
(round) => round.panorama.countryCode
);
const playerTeam = pageProps.game.teams.find((team) =>
team.players.some((player) => player.playerId === playerId)
);
const playerGuesses = playerTeam.players.find(
(player) => player.playerId === playerId
).guesses;
const playerResults = playerGuesses.map((guess, index) => {
return {
countryCode: countryCodes[index],
score: guess.score,
distance: guess.distance,
mapName: pageProps.game.options.map.name,
gameMode,
gameType: pageProps.game.gameType,
};
});
return playerResults;
};
const getMyUserId = async () => {
const myGames = await getMyGames();
return myGames.entries[0].user.id;
};
const getMyResults = async (count) => {
const myDuels = await getMyLastGames(count);
const myUserId = await getMyUserId();
const fullResults = [];
console.log(`Loading ${myDuels.length} games`)
for (const gameId of myDuels) {
const playerResults = await getPlayerResults(gameId, myUserId);
await new Promise(res => setTimeout(res, 1000 * Math.random() + 500))
fullResults.push(...playerResults);
}
return fullResults;
};
const regionNames = new Intl.DisplayNames(['en'], {type: 'region'});
const getStatistics = async (results, field, order) => {
const duelsResults = results.filter(e => e.gameType === 'Duels');
const resultsByGameMode = Object.groupBy(duelsResults, (e) => e.gameMode);
const result = [];
result.push(`Statistics for ${duelsResults.length} guesses by field ${field}\n`);
Object.entries(resultsByGameMode).forEach(([gameMode, results]) => {
result.push(`${gameMode} (guesses: ${results.length})`);
const resultsByCountry = Object.groupBy(results, (e) => e.countryCode);
const countryScores = Object.entries(resultsByCountry).map(
([countryCode, countryResults]) => {
const countryAverage =
countryResults.reduce((acc, e) => acc + e[field], 0) /
(countryResults.length ?? 1);
return [countryCode, countryAverage.toFixed(0), countryResults.length];
}
);
countryScores
.sort((a, b) => order === 'asc' ? b[1] - a[1]: a[1] - b[1])
.forEach(([countryCode, countryAverage, countryCount]) => {
result.push(`${regionNames.of(countryCode.toUpperCase())} (guesses: ${countryCount}): ${countryAverage}`);
});
result.push('\n');
});
console.log(result.join('\n'));
};
const myResults = await getMyResults(100);
await getStatistics(myResults, 'score', 'desc');
await getStatistics(myResults, 'distance', 'asc');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment