// ==UserScript==
// @name Geoguessr Map-Making Auto-Tag
// @namespace http://tampermonkey.net/
// @version 3.1
// @description Tag your street views by date&address&generations
// @author KaKa
// @match https://map-making.app/maps/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_setClipboard
// @license MIT
// @icon https://www.google.com/s2/favicons?domain=geoguessr.com
// ==/UserScript==
(function() {
'use strict';
async function runScript(tags) {
const option = confirm('Do you want to input data from the clipboard? If you click "Cancel", you will need to upload a JSON file.');
let data;
if (option) {
const text = await navigator.clipboard.readText();
try {
data = JSON.parse(text);
} catch (error) {
alert('The input JSON data is invalid or incorrectly formatted.');
return;
}
} else {
const input = document.createElement('input');
input.type = 'file';
document.body.appendChild(input);
data = await new Promise((resolve) => {
input.addEventListener('change', async () => {
const file = input.files[0];
const reader = new FileReader();
reader.onload = (event) => {
try {
const result = JSON.parse(event.target.result);
resolve(result);
document.body.removeChild(input);
} catch (error) {
alert('The input JSON data is invalid or incorrectly formatted.');
}
};
reader.readAsText(file);
});
input.click();
});
}
const newData = [];
async function UE(t, e) {
try {
const r = `https://maps.googleapis.com/$rpc/google.internal.maps.mapsjs.v1.MapsJsInternalService/${t}`;
let payload=createPayload(t,e)
const response = await fetch(r, {
method: "POST",
headers: {
"content-type": "application/json+protobuf",
"x-user-agent": "grpc-web-javascript/0.1"
},
body: payload,
mode: "cors",
credentials: "omit"
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return await response.json();
}
} catch (error) {
console.error(`There was a problem with the UE function: ${error.message}`);
}
}
function createPayload(mode,coorData) {
let payload;
if (mode === 'GetMetadata') {
payload = [["apiv3",null,null,null,"US",null,null,null,null,null,[[0]]],["en","US"],[[[2,coorData.panoId]]],[[1,2,3,4,8,6]]];
} else if (mode === 'SingleImageSearch') {
payload =[["apiv3",null,null,null,"US",null,null,null,null,null, [[0]]], [[null,null,coorData.lat,coorData.lng],50], [null,["en","US"],null,null,null,null,null,null,[2],null,[[[2,1,2],[3,1,2],[10,1,2]]]], [[1,2,3,4,8,6]]];
} else {
throw new Error("Invalid mode!");
}
return JSON.stringify(payload);
}
function getMetaData(svData) {
if (svData) {
let levelId=svData.dn
let year = 'noyear',month = 'nomonth'
let panoType='Unofficial'
let subdivision='nosub',locality='nolocality'
if (svData.imageDate) {
const matchYear = svData.imageDate.match(/\d{4}/);
if (matchYear) {
year = matchYear[0];
}
const matchMonth = svData.imageDate.match(/-(\d{2})/);
if (matchMonth) {
month = matchMonth[1];
}
}
if (svData.copyright.includes('Google')) {
panoType = 'Official';
}
if(svData.location.description){
let parts = svData.location.description.split(',');
if(parts.length > 1){
subdivision = parts[parts.length-1].trim();
locality = parts[parts.length-2].trim();
} else {
subdivision = svData.location.description;
}
}
return [year,month,panoType,subdivision,locality,levelId]
}
else{
return null}
}
function getGeneration(svData,country) {
if (svData&&svData.tiles) {
if (svData.tiles.worldSize.height === 1664) { // Gen 1
return 'Gen1';
} else if (svData.tiles.worldSize.height === 6656) { // Gen 2 or 3
let lat;
for (let key in svData.Sv) {
lat = svData.Sv[key].lat;
break;
}
let date;
if (svData.imageDate) {
date = new Date(svData.imageDate);
} else {
date = 'nodata';
}
if (date!=='nodata'&&((country === 'BD' && (date >= new Date('2021-04'))) ||
(country === 'EC' && (date >= new Date('2022-03'))) ||
(country === 'FI' && (date >= new Date('2020-09'))) ||
(country === 'IN' && (date >= new Date('2021-10'))) ||
(country === 'LK' && (date >= new Date('2021-02'))) ||
(country === 'KH' && (date >= new Date('2022-10'))) ||
(country === 'NG' && (date >= new Date('2021-06'))) ||
(country === 'ST') ||
(country === 'US' && lat > 52 && (date >= new Date('2019-01'))))) {
return 'Shitcam';
}
if ((country === 'AU' || 'BR'||'CA' || 'CL'|| 'JP' || 'GB' || 'IE' || 'NZ'
|| 'MX'|| 'RU' || 'US' || 'IT'||'DK' || 'GR' || 'RO' || 'PL'
|| 'CZ' )|| 'CH'|| 'SE' || 'FI'|| 'BE' || 'LU' || 'NL' || 'ZA'
|| 'SG' || 'TW' || 'HK' || 'MO'||'MC' || 'SM' || 'AD' || 'IM'
||'JE'||'FR' ||'DE' || 'ES'|| 'PT' ) {
return 'Gen2or3';
}
return 'Gen3';
} else if(svData.tiles.worldSize.height === 8192){
return 'Gen4';
}
}
return 'Unknown';
}
var CHUNK_SIZE = 1200;
var promises = [];
async function processCoord(coord, tags, svData,ccData) {
if (!coord.extra) {
coord.extra = {};
}
if (!coord.extra.tags) {
coord.extra.tags = [];
}
if (svData){
let meta=getMetaData(svData)
let yearTag=meta[0]
let monthTag=meta[1]
let typeTag=meta[2]
let subdivisionTag=meta[3]
let localityTag=meta[4]
let countryTag
let genTag
let trekkerTag=meta[5]
if (ccData){
try {
countryTag = ccData[1][0][5][0][1][4]
}
catch (error) {
try {
countryTag = ccData[1][5][0][1][4]
} catch (error) {
countryTag='nocountry'
}
}
if (!countryTag)countryTag='nocountry'
}
genTag = getGeneration(svData,countryTag)
if (tags.includes('generation')&&typeTag=='Official')coord.extra.tags.push(genTag)
if (tags.includes('year'))coord.extra.tags.push(yearTag)
if (tags.includes('month'))coord.extra.tags.push(monthTag)
if (tags.includes('type'))coord.extra.tags.push(typeTag)
if (tags.includes('type')&&trekkerTag&&typeTag=='Official')coord.extra.tags.push('trekker')
if (tags.includes('country')&&typeTag=='Official')coord.extra.tags.push(countryTag)
if (tags.includes('subdivision')&&typeTag=='Official')coord.extra.tags.push(subdivisionTag)
if (tags.includes('locality')&&typeTag=='Official')coord.extra.tags.push(localityTag)
}
else {coord.extra.tags.push('nopano')}
if (coord.extra.tags) {coord.extra.tags=Array.from(new Set(coord.extra.tags))}
newData.push(coord);
}
async function processChunk(chunk, tags) {
var service = new google.maps.StreetViewService();
var promises = chunk.map(async coord => {
let panoId = coord.panoId;
let latLng = {lat: coord.lat, lng: coord.lng};
let svData;
let ccData;
if ((panoId || latLng)) {
svData = await getSVData(service, panoId ? {pano: panoId} : {location: latLng, radius: 50});
}
if (!panoId && (tags.includes('generation')||('country'))) {
ccData = await UE('SingleImageSearch', coord);
} else if (panoId) {
ccData = await UE('GetMetadata', coord);
}
await processCoord(coord, tags, svData,ccData)
});
await Promise.all(promises);
}
function getSVData(service, options) {
return new Promise(resolve => service.getPanorama({...options}, (data, status) => {
resolve(data);
}));
}
async function processData(tags) {
try {
for (let i = 0; i < data.customCoordinates.length; i += CHUNK_SIZE) {
let chunk = data.customCoordinates.slice(i, i + CHUNK_SIZE);
await processChunk(chunk, tags);
}
GM_setClipboard(JSON.stringify(newData));
alert("New JSON data has been copied to the clipboard!");
} catch (error) {
alert("Invalid JSON data");
console.error('Error processing JSON data:', error);
}
}
processData(tags);
}
var mainButtonContainer = document.createElement('div');
mainButtonContainer.style.position = 'fixed';
mainButtonContainer.style.right = '20px';
mainButtonContainer.style.bottom = '20px';
document.body.appendChild(mainButtonContainer);
var buttonContainer = document.createElement('div');
buttonContainer.style.position = 'fixed';
buttonContainer.style.right = '20px';
buttonContainer.style.bottom = '60px';
buttonContainer.style.display = 'none';
document.body.appendChild(buttonContainer);
function createCheckbox(text, tags) {
var label = document.createElement('label');
var checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.value = text;
checkbox.name = 'tags';
checkbox.id = tags;
label.appendChild(checkbox);
label.appendChild(document.createTextNode(text));
buttonContainer.appendChild(label);
return checkbox;
}
var triggerButton = document.createElement('button');
triggerButton.textContent = 'Star Tagging';
triggerButton.addEventListener('click', function() {
var checkboxes = document.getElementsByName('tags');
var checkedTags = [];
for (var i=0; i<checkboxes.length; i++) {
if (checkboxes[i].checked) {
checkedTags.push(checkboxes[i].id);
}
}
runScript(checkedTags);
});
buttonContainer.appendChild(triggerButton);
var mainButton = document.createElement('button');
mainButton.textContent = 'Auto-Tag';
mainButton.addEventListener('click', function() {
if (buttonContainer.style.display === 'none') {
buttonContainer.style.display = 'block';
} else {
buttonContainer.style.display = 'none';
}
});
mainButtonContainer.appendChild(mainButton);
createCheckbox('Year', 'year');
createCheckbox('Month', 'month');
createCheckbox('Type', 'type');
createCheckbox('Country', 'country');
createCheckbox('Subdivision', 'subdivision');
createCheckbox('Locality', 'locality');
createCheckbox('Generations', 'generation');
})();