Last active
May 20, 2025 08:03
-
-
Save Aracturat/48d9ae6aae487c50046207228962046e to your computer and use it in GitHub Desktop.
Geoguessr statistics
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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