Skip to content

Instantly share code, notes, and snippets.

@Aracturat
Last active July 6, 2025 19:10
Show Gist options
  • Save Aracturat/08b29ab7d00374f5f501b67443919b43 to your computer and use it in GitHub Desktop.
Save Aracturat/08b29ab7d00374f5f501b67443919b43 to your computer and use it in GitHub Desktop.
Create geoguessr map with bad rounds
async function createMap(name) {
const response = await fetch('https://www.geoguessr.com/api/v4/user-maps/drafts', {
headers: {
accept: '*/*',
'content-type': 'application/json',
},
body: JSON.stringify({ name, mode: 'coordinates' }),
method: 'POST',
mode: 'cors',
credentials: 'include',
}).then((e) => e.json());
return response.id;
}
async function updateCoordinates(id, coordinates, version) {
await fetch(`https://www.geoguessr.com/api/v4/user-maps/drafts/${id}`, {
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
avatar: { background: 'morning', decoration: 'palmtrees', ground: 'green', landscape: 'grassmountains' },
regions: [],
customCoordinates: coordinates,
tags: [],
maxErrorDistance: 17499125,
hasCustomErrorDistance: false,
version,
}),
method: 'PUT',
mode: 'cors',
credentials: 'include',
});
}
async function publishMap(id) {
await fetch(`https://www.geoguessr.com/api/v4/user-maps/drafts/${id}/publish/`, {
headers: {
'content-type': 'application/json',
},
body: '{}',
method: 'PUT',
mode: 'cors',
credentials: 'include',
});
}
async function createAndPublishMap(name, coordinates) {
const id = await createMap(name);
await updateCoordinates(id, coordinates, 1);
await publishMap(id);
return `https://www.geoguessr.com/maps/${id}`;
}
const getMyGames = (paginationToken) => {
return fetch(`https://www.geoguessr.com/api/v4/feed/private?count=10&paginationToken=${paginationToken}`, {
headers: {
'content-type': 'application/json',
},
method: 'GET',
credentials: 'include',
}).then((e) => e.json());
};
const getMyLastDuels = 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.slice(0, count);
};
async function getBadPanaramas(userId, gameId, minScore, maxScore, gameMode) {
const out = await fetch(`https://game-server.geoguessr.com/api/duels/${gameId}`, { credentials: 'include' }).then(
(res) => res.json()
);
let player_index;
if (out.teams[0].players[0].playerId === userId) {
player_index = 0;
} else if (out.teams[1].players[0].playerId === userId) {
player_index = 1;
} else {
return [];
}
const outGameMode = out.movementOptions.forbidZooming
? 'NMPZ'
: out.movementOptions.forbidMoving
? 'NO MOVE'
: 'MOVE';
if (outGameMode !== gameMode && gameMode !== 'ANY') {
return [];
}
return out.teams[player_index].players[0].guesses
.filter((e) => e.score <= maxScore && e.score >= minScore)
.map((e) => e.roundNumber)
.map((e) => out.rounds[e - 1].panorama);
}
const analyzeAndCreateMap = async (gameMode, minScore, maxScore, lastGamesCount, countryCode) => {
const myDuels = await getMyLastDuels(lastGamesCount);
const userId = JSON.parse(document.getElementById('__NEXT_DATA__').innerText).props.accountProps.account.user.userId;
let badPanoramas = [];
for (let i = 0; i < myDuels.length; i++) {
console.log(`Evaluate game ${i + 1}/${myDuels.length}`);
const gameId = myDuels[i];
const duelBadPanaramas = await getBadPanaramas(userId, gameId, minScore, maxScore, gameMode);
await new Promise((resolve) => setTimeout(resolve, 1000));
badPanoramas.push(...duelBadPanaramas);
}
let mapName = `Rounds with score in [${minScore}, ${maxScore}] in the last ${lastGamesCount} ${gameMode} games`;
if (countryCode) {
badPanoramas = badPanoramas.filter((e) => e.countryCode === countryCode);
mapName += ` for ${countryCode}`;
}
if (badPanoramas.length < 5) {
console.log('Увеличьте кол-во игр, так как нужно минимум 5 раундов для создания карты');
return;
}
const link = await createAndPublishMap(mapName, badPanoramas);
console.log(link);
};
// Варианты игр 'NMPZ' 'NO MOVE' 'MOVE' 'ANY'
// Последний параметр - страна, если нужно создать карту только для одной страны, к примеру 'ar'
analyzeAndCreateMap('ANY', 1000, 3000, 100, null);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment