// ==UserScript== // @name Geoguessr Duels Country Score Script // @description Keeps track of your score for each country in duels games and displays it on an interactive map on your profile // @author nappyslappy // @version 0.0 // @include /^(https?)?(\:)?(\/\/)?([^\/]*\.)?geoguessr\.com($|\/.*)/ // @grant GM_addStyle // @license MIT // @namespace Geoguessr Duels Country Score Script // @downloadURL none // ==/UserScript== //Published: June 6th, 2022 //Last Update: //**************************** INFORMATION ****************************// /* */ /* This script was mainly made for you to have a visual map of */ /* which countries you know well and which ones you need to improve on */ /* */ /* The script will monitor your duels guesses and keep a unique record */ /* for each country of the times you guessed it correctly */ /* */ /* If there is a round where you don't make a guess, */ /* it wont count against you. The script will only keep track */ /* of your record when you DO make a guess */ /* */ /* You can view the map showing your percentage score for each */ /* country at the bottom of your profile page */ /* */ /* Some countries are too small to show on the map so there is */ /* a search bar on the left hand side to filter through them */ /* */ /***********************************************************************/ //******************************* SETUP *******************************// /* If you haven't already, download the free extension 'tampermonkey' */ /* and paste this entire script into a blank page there. */ /* */ /* This will allow the script to interact with geoguessr */ /* and keep track of your scores */ /* */ /* Go to www.bigdatacloud.com, create a free account, */ /* and click 'Get your free API key' */ /* */ /* Copy and paste that key below where it says 'API_KEY' */ /* */ /* Go to your profile on Geoguessr and find your user ID under */ /* where it says 'sign out' (The last part after 'user/' is the ID) */ /* */ /* Copy and paste your ID below where it says 'USER_ID' */ /* */ /* Optionally, you can change the percentages */ /* where it says 'Cusomizable Stuff' below */ /* */ /* Now just play duels and see your new scores updated on your profile */ /* */ /***********************************************************************/ //**************************** INSERT YOUR INFO HERE ****************************// let API_KEY = ''; //Put your free api key inside '' let USER_ID = ''; //Put your user id inside '' //**************************** CUSTOMIZABLE STUFF ****************************// let green_range = 0.75; //Everything above this percentage will be green on the map let yellow_range = 0.25; //Everything above this percentage will be yellow on the map let red_range = 0; //Everything above this percentage will be red on the map //**************************** START OF SCRIPT ****************************// //Global Variables let alerted = false; let profile = false; let player_index = 0; let previous_last_guess_number = 0; function evaluate(guess,location,distance){ 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_average = ((distance_average * distance_number) + distance) / (distance_number + 1); distance_number = distance_number + 1; window.localStorage.setItem(`${location}-distance-average-${USER_ID}`,distance_average); window.localStorage.setItem(`${location}-distance-number-${USER_ID}`,distance_number); } }; async function getGuessCountryCode(location){ if(location[0] <= -85.05 || location == null){ return 'AQ'; } else{ let api = "https://api.bigdatacloud.net/data/reverse-geocode?latitude="+location[0]+"&longitude="+location[1]+"&localityLanguage=en&key="+API_KEY; let country_code = await fetch(api) .then(res => res.json()) .then((out) => { return out.countryCode; }) return country_code; }; }; async function checkGuess(api){ return new Promise((resolve,reject) => { fetch(api,{credentials: 'include'}) .then((res) => res.json()) .then((out) => { 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{ if(!alerted){ alert('Duels Record Script:\nMAKE SURE YOU HAVE THE CORRECT USER ID'); alerted = true; } return; } let last_guess_number = out.teams[player_index].players[0].guesses.length; //If a new guess hasn't been made if(last_guess_number == previous_last_guess_number){ return; } else if(out.teams[player_index].players[0].guesses[last_guess_number-1].roundNumber !== out.currentRoundNumber){ return; } previous_last_guess_number = last_guess_number; let current_round = out.currentRoundNumber; let current_guess = [out.teams[player_index].players[0].guesses[last_guess_number-1].lat, out.teams[player_index].players[0].guesses[last_guess_number-1].lng]; getGuessCountryCode(current_guess) .then((guess) => { let location_code = out.rounds[current_round-1].panorama.countryCode.toUpperCase(); let distance = out.teams[player_index].players[0].guesses[last_guess_number-1].distance; evaluate(guess,location_code,distance); resolve(out); }); }) .catch((error) => { reject(error); }); }); } function profileCheck(){ if(location.pathname.endsWith('/profile') && !profile){ profileButton(); } else if(!location.pathname.endsWith('/profile') && profile){ profile = false; document.getElementById('map-wrapper-element').remove(); document.getElementById('css-for-map').remove(); } } function duelsCheck(){ if(!location.pathname.startsWith('/duels/') || location.pathname.endsWith('/summary')){ return; } const game_tag = window.location.href.substring(window.location.href.lastIndexOf('/') + 1); const api_url = "https://game-server.geoguessr.com/api/duels/"+game_tag; checkGuess(api_url); }; setInterval(duelsCheck,500); setInterval(profileCheck,500); /************************************************** MAP STUFF HERE **************************************************/ function profileButton(){ profile = true; const svgMap = document.createElement('div'); svgMap.setAttribute('id','map-wrapper-element'); const cssForMap = document.createElement('style'); cssForMap.setAttribute('id', 'css-for-map'); const scriptForMap = document.createElement('script'); scriptForMap.setAttribute('id', 'script-for-map'); cssForMap.textContent = ` .map-wrapper-element{ position: relative; left: 0px; } #map-title{ font-size: 30px; text-align: center; padding-bottom: 20px; color: var(--ds-color-yellow-50); font-family: var(--font-neo-sans); } #total-percentage-wrapper{ width: 100%; background-color: gray; height: 50px; position: relative; } #total-percentage-text-wrapper{ position: absolute; left: 0; right: 0; height: 100%; } #total-percentage-text{ display: flex; font-size: 20px; height: 100%; justify-content: center; align-items: center; color: black; } #total-percentage-bar{ width: 50%; height: 100%; background-color: gold; } .display-wrapper{ margin: 0 auto; padding: 10px; width: fit-content; font-size: 20px; } .display-wrapper #display-country-details{ visibility: show; color: white; } .display-wrapper #display-country-distance{ visibility: show; font-size: 16px; display: flex; align-items: center; justify-content: center; } .map-wrapper{ margin: 0 auto; pointer-events: none; padding-bottom: 25px; } #world-map { display: block; position: relative; top: 0; left: 0; width: 100%; height: 100%; pointer-events: auto; transform: scale(1); transform-origin: 0px 0px; z-index: 2; } #world-map path{ stroke: white; fill: black; transition: fill 0.3s ease; pointer-events: all; } #world-map path:hover{ fill: rgb(175, 175, 175); pointer-events: all; } #details-box { padding: 1rem; border-radius: 8px; font-size: 14px; position: fixed; color: white; font-family: "Poppins"; background-color: gray; width: fit-content; transform: translateX(-50%); transition: opacity .4s ease; z-index: 3; } #unit-selector{ padding-left: 50px; } #search-bar-wrapper{ pointer-events: all; position: absolute; z-index: 3; } #search-bar { box-sizing: border-box; z-index: 3; } #search-bar-list { list-style: none; display: none; z-index: 3; } #search-bar-list li { padding: 10px; width: 73.5%; border-bottom: 2px solid #ffffff; z-index: 3; } #search-bar-list li:hover { background: #dddddd; } `; svgMap.innerHTML = `

Duels Country Scores



Country Name: 0 / 0 (0%)

Average Distance: 0.0 km

`; scriptForMap.textContent = ` { let countries = ['AF','AX','AL','DZ','AS','AD','AO','AI','AQ','AG','AR','AM','AW','AU','AT','AZ','BS','BH','BD','BB','BY','BE','BZ','BJ','BM','BT','BO','BQ','BA','BW','BV','BR','IO','BN','BG','BF','BI','KH','CM','CA','CV','KY','CF','TD','CL','CN','CX','CC','CO','KM','CG','CD','CK','CR','CI','HR','CU','CW','CY','CZ','DK','DJ','DM','DO','EC','EG','SV','GQ','ER','EE','ET','FK','FO','FJ','FI','FR','GF','PF','TF','GA','GM','GE','DE','GH','GI','GR','GL','GD','GP','GU','GT','GG','GN','GW','GY','HT','HM','VA','HN','HK','HU','IS','IN','ID','IR','IQ','IE','IM','IL','IT','JM','JP','JE','JO','KZ','KE','KI','KP','KR','XK','KW','KG','LA','LV','LB','LS','LR','LY','LI','LT','LU','MO','MK','MG','MW','MY','MV','ML','MT','MH','MQ','MR','MU','YT','MX','FM','MD','MC','MN','ME','MS','MA','MZ','MM','NA','NR','NP','NL','NC','NZ','NI','NE','NG','NU','NF','MP','NO','OM','PK','PW','PS','PA','PG','PY','PE','PH','PN','PL','PT','PR','QA','RS','RE','RO','RU','RW','BL','SH','KN','LC','MF','VC','WS','ST','SA','SN','SC','SL','SG','SX','SK','SI','SB','SO','ZA','GS','SS','ES','LK','SD','SR','SJ','SZ','SE','CH','SY','TW','TJ','TZ','TH','TL','TG','TK','TO','TT','TN','TR','TM','TC','TV','UG','UA','AE','GB','US','UM','UY','UZ','VU','VE','VN','VG','VI','WF','EH','YE','ZM','ZW','SM']; let once = false; document.getElementById("details-box").style.opacity = "0%"; //UNIT SELECTION if(window.localStorage.getItem('units-svg-map') === 'mi'){ document.getElementById('unit-selector-mi').checked = true; } else if(window.localStorage.getItem('units-svg-map') === 'km'){ document.getElementById('unit-selector-km').checked = true; } document.getElementById('unit-selector-mi').addEventListener('click', function(){ window.localStorage.setItem('units-svg-map', 'mi'); }); document.getElementById('unit-selector-km').addEventListener('click', function(){ window.localStorage.setItem('units-svg-map', 'km'); }); //MAP SETUP document.getElementById('map-wrapper-element').addEventListener('mousemove', function(e){ if(once){ if(window.location.pathname.endsWith('/profile')){ return; } once = false; } if(!window.location.pathname.endsWith('/profile')){ return; } once = true; //Percentage Bar let correct_total = parseInt(window.localStorage.getItem('overall-correct-${USER_ID}')); let total_total = parseInt(window.localStorage.getItem('overall-total-${USER_ID}')); let percentage_total = Math.floor((correct_total / total_total) * 100); document.getElementById('total-percentage-bar').style.width = percentage_total + '%'; document.getElementById('total-percentage-text').innerHTML = 'Overall Score: ' + correct_total + ' / ' + total_total + ' (' + percentage_total +'%) '; if(percentage_total >= 100*${green_range}){ document.getElementById('total-percentage-bar').style.backgroundColor = 'green'; } else if(percentage_total >= 100*${yellow_range}){ document.getElementById('total-percentage-bar').style.backgroundColor = 'yellow'; } else if(percentage_total >= 100*${red_range}){ document.getElementById('total-percentage-bar').style.backgroundColor = 'red'; } //Country colors countries.forEach(function(country){ let correct = 0; let total = 0; if(window.localStorage.getItem(country + '-number-correct-${USER_ID}') !== null){ correct = window.localStorage.getItem(country + '-number-correct-${USER_ID}'); } if(window.localStorage.getItem(country + '-number-total-${USER_ID}') !== null){ total = window.localStorage.getItem(country + '-number-total-${USER_ID}'); } let percentage = parseInt(correct) / parseInt(total); if(correct === 0 && total === 0){ percentage = 'none'; } let targetCountry = document.getElementById(country); if(!targetCountry){ return; } //Setting the countries to a color based on their percentage if(percentage >= ${green_range}){ targetCountry.setAttribute('style', 'fill: lightgreen'); //green } else if(percentage >= ${yellow_range}){ targetCountry.setAttribute('style', 'fill: rgb(233, 233, 84)'); //yellow } else if(percentage >= ${red_range}){ targetCountry.setAttribute('style', 'fill: lightcoral'); //red } else{ targetCountry.setAttribute('style', 'fill: black'); } }); }); function mouseOverCountry(country){ if(typeof country.target !== 'undefined'){ country = country.target; } if (country.tagName == 'path'){ let content = country.dataset.name; document.getElementById("details-box").innerHTML = content; document.getElementById("details-box").style.opacity = "100%"; //Changing the color when you hover over a country if(country.style.fill == 'lightcoral'){ country.setAttribute('style', 'fill:red'); } else if(country.style.fill == 'lightgreen'){ country.setAttribute('style', 'fill:green'); } else if(country.style.fill == 'rgb(233, 233, 84)'){ country.setAttribute('style', 'fill:yellow'); } else if(country.style.fill == 'black'){ country.setAttribute('style', 'fill:rgb(175, 175, 175)'); } //Displaying the country and the stats at the top of the page let countryName = country.dataset.name; let countryCorrect = 0; let countryTotal = 0; let countryPercentage = 0; let countryDistance = 0.0; let distanceUnit = 'km'; let target = country.id; if(window.localStorage.getItem(target + '-number-correct-${USER_ID}') !== null){ countryCorrect = window.localStorage.getItem(target + '-number-correct-${USER_ID}'); } if(window.localStorage.getItem(target + '-number-total-${USER_ID}') !== null){ countryTotal = window.localStorage.getItem(target + '-number-total-${USER_ID}'); } if((countryCorrect !== 0) && (countryTotal !== 0)){ countryPercentage = Math.floor(100 * (parseInt(countryCorrect) / parseInt(countryTotal))); } if(window.localStorage.getItem(target + '-distance-average-${USER_ID}') !== null){ countryDistance = parseInt(window.localStorage.getItem(target + '-distance-average-${USER_ID}')) / 1000; if(document.getElementById('unit-selector-mi').checked){ countryDistance = countryDistance / 1.609; } countryDistance = round(countryDistance,1); } if(document.getElementById('unit-selector-mi').checked){ distanceUnit = 'mi'; } document.getElementById("display-country-details").textContent = countryName + ": " + countryCorrect + " / " + countryTotal + " (" + countryPercentage + "%)"; document.getElementById("display-country-details").style.visibility = "visible"; document.getElementById("display-country-distance").textContent = 'Average Distance: ' + countryDistance + ' ' + distanceUnit; document.getElementById("display-country-distance").style.visibility = "visible"; } else { document.getElementById("details-box").style.opacity = "0%"; } }; function mouseOutCountry(country){ if(typeof country.target !== 'undefined'){ country = country.target; } if (country.tagName == 'path'){ //Changing the color when you stop hovering over a country if(country.style.fill == 'red'){ country.setAttribute('style', 'fill:lightcoral'); } if(country.style.fill == 'green'){ country.setAttribute('style', 'fill:lightgreen'); } if(country.style.fill == 'yellow'){ country.setAttribute('style', 'fill:rgb(233, 233, 84)'); } if(country.style.fill == 'rgb(175, 175, 175)'){ country.setAttribute('style', 'fill:black'); } //Hiding the display at the top when no country is hovering document.getElementById("display-country-details").style.visibility = "hidden"; document.getElementById("display-country-distance").style.visibility = "hidden"; } }; //DETAILS BOX var tooltipSpan = document.getElementById('details-box'); window.onmousemove = function(e){ var x = e.clientX, y = e.clientY; tooltipSpan.style.top = (y + 20) + 'px'; tooltipSpan.style.left = (x) + 'px'; }; function round(value, precision) { let multiplier = Math.pow(10, precision || 0); return Math.round(value * multiplier) / multiplier; }; //REGULAR LISTENERS document.getElementById('map-wrapper-element').addEventListener('mouseover', mouseOverCountry); document.getElementById('map-wrapper-element').addEventListener('mouseout', mouseOutCountry); //SEARCH LIST function mouseOverList(e){ let temp = e.target.id.toUpperCase(); let target_country = document.getElementById(temp); mouseOverCountry(target_country); } function mouseOutList(e){ let temp = e.target.id.toUpperCase(); let target_country = document.getElementById(temp); mouseOutCountry(target_country); } document.getElementById('search-bar').addEventListener('click', () => { let search_bar = document.getElementById('search-bar'); let list = document.querySelectorAll('#search-bar-list li'); list.forEach(item => item.addEventListener('mouseover', mouseOverList)); list.forEach(item => item.addEventListener('mouseout', mouseOutList)); search_bar.onkeyup = () => { let search = search_bar.value.toLowerCase(); let count = 0; if(search_bar.value !== ''){ document.getElementById('search-bar-list').style.display = 'inline'; } else{ document.getElementById('search-bar-list').style.display = 'none'; } //Showing the results that match and hiding those that dont (max 6 items) for(let i of list){ let item = i.innerHTML.toLowerCase(); if(item.indexOf(search) == -1){ i.style.display = 'none'; } else{ if(count < 6){ count = count + 1; i.style.display = 'block'; } else{ i.style.display = 'none'; } } }; }; }); }`; let referenceNode = document.getElementsByClassName('version3_main__xNkED')[0]; let siblingNode = document.getElementsByClassName('footer_footer__NmtmJ')[0]; referenceNode.parentNode.insertBefore(svgMap, siblingNode); document.head.appendChild(cssForMap); document.getElementById('world-map').appendChild(scriptForMap); }; //**************************** END OF SCRIPT ****************************//