Last active
May 20, 2025 20:03
-
-
Save Aracturat/e39914220b418ccbd60c5b53e49a2461 to your computer and use it in GitHub Desktop.
geostats-ranked-duels.js
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
//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