GeoGuessr Country Streaks (Settings Page)

A fork of Jupaoqq's GeoGuessr country streak counter. Count streaks and save results to your maps. Includes a preferences page for ease of use.

// ==UserScript==
// @name         GeoGuessr Country Streaks (Settings Page)
// @version      0.4.1
// @author       Han75, Jupaoqq
// @license      MIT
// @description  A fork of Jupaoqq's GeoGuessr country streak counter. Count streaks and save results to your maps. Includes a preferences page for ease of use. 
// @match        https://www.geoguessr.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
// @namespace    https://greasyfork.org/en/users/973646
// ==/UserScript==
 
// Credits: victheturtle, subsymmetry, slashP, emilyapocalypse
 
// ------------------------------------------------- MUST READ BELOW -------------------------------------------------


/**
 * This version of the script is modified so that these settings can be changed at any time from the preferences page 
 * To open the preferences page, simply click on the score counter icon while in game,
 * or click on the tab that reads "Open Streak Counter Preferences" from the round results.
 * Alternatively, you can change the values below, but this is NOT RECOMMENDED
*/
let ENABLED_ON_CHALLENGES = false; // Replace with true or false
let API_Key = 'ENTER_API_KEY_HERE'; 

let AUTOMATIC = true; // Replace with false for a manual counter. Without an API key, the counter will still be manual
 
// Map number: e.g. Capitals of the World (Bing Satellite [20]), link https://www.geoguessr.com/maps/62062fcf0f38ba000190be65, has map number of 62062fcf0f38ba000190be65.


/**
 * Manually Save Locations:
 * Press the z key (or change it to any other key) or click "save location" to save the location into a map of yours.
 *
 * You may replace manualKey with any key on your keyboard (by default it's key z).
 * e.g. do "let manualKey = 'x'; " will remap the key to x instead.
 * Press this key to save this location when the round result appears.
 *
 * You must replace MAP_LINK_HERE with your map number.
 * e.g. do "let manualSave = "61a189a5531c7c4d38a6ae1"; " will save locations to map https://www.geoguessr.com/maps/61a189a5531c7c4d38a6ae1
 * Such map must contain at least 5 unique locations.
 *
 */
 
let manualSave = "MAP_LINK_HERE";
let manualKey = 'z';

// --------------------------------------------------------------------------------------------------------------------
 
 
/**
 * Advanced Options
 */
 
// More than one option below may be set to true, and multiple values may use the same map number.
 
/**
 * goodGuesses:
 * For locations that you guessed the country correctly and received more points than the cutoff specified below.
 *
 * Replace MAP_LINK_HERE with your map number, e.g. do "let goodGuesses = "61a189a5531c7c4d38a6ae1"; "
 * Such map must contain at least 5 unique locations.
 *
 * To turn in on, do "let collectGoodGuesses = true;" To turn it off, do "let collectGoodGuesses = false;"
 * To change cutoff, do e.g. "let cutOffGood = 3500;" so every score higher than 3500 points (and you have to guess the country correctly) goes to this map.)
 */
 
let goodGuesses = "MAP_LINK_HERE";
let collectGoodGuesses = false;
let cutOffGood = 4000;
 
/**
 * okGuesses:
 * For locations that you guessed the country correctly and received less points than the cutoff specified below.
 *
 * Replace MAP_LINK_HERE with your map number, e.g. do "let okGuesses = "61a189a5531c7c4d38a6ae1"; "
 * Such map must contain at least 5 unique locations.
 *
 * To turn in on, do "let collectOkGuesses = true;" To turn it off, do "let collectOkGuesses = false;"
 * To change cutoff, do e.g. "let cutOffOk = 3500;" so every score lower than 3500 points (and you have to guess the country correctly) goes to this map.)
 */
 
let okGuesses = "MAP_LINK_HERE";
let collectOkGuesses = false;
let cutoffOk = 4000;
 
/**
 * badGuesses:
 * For locations that you guessed the country incorrectly.
 *
 * Replace MAP_LINK_HERE with your map number, e.g. do "let badGuesses = "61a189a5531c7c4d38a6ae1"; "
 * Such map must contain at least 5 unique locations.
 *
 * To turn in on, do "let collectBadGuesses = true;" To turn it off, do "let collectBadGuesses = false;"
 */
 
let badGuesses = "MAP_LINK_HERE";
let collectBadGuesses = false;
 
/**
 * GoodText: shows this text in result screen if you guess the country correctly with score exceeding your desired cutoff score.
 * OkText: shows this text in result screen if you guess the country correctly with score below your desired cutoff score.
 * BadText: shows this text in result screen if you guess the country incorrectly.
 * SaveText: shows this text in result screen if you manually saved the location.
 * defaultText: shows this text in result screen to remind you the manual option.
 * All of these fields are customizable, you may replace it with your custom text.
 */
 
let GoodText = "Location has been saved to your Good Guesses Map.";
let OkText = "Location has been saved to your Ok Guesses Map.";
let BadText = "Location has been saved to your Bad Guesses Map.";
let SaveText = "Location has been manually saved to your Map.";
let defaultText = "";
 
// Do not need to modify any code below.

//Try to read saved api key from local storage
if(API_Key == 'ENTER_API_KEY_HERE'&&localStorage.getItem("_STREAK_API_KEY")!=null){
    //Comment out the line below if you want to use a different key. 
    API_Key = localStorage.getItem("_STREAK_API_KEY");
}else if(!(API_Key.length <= 24 || API_Key.match("^[a-fA-F0-9_]*$") == null)){
    localStorage.setItem("_STREAK_API_KEY",API_Key);
};
//try to read saved map ids from local storage
manualSave = (manualSave=="MAP_LINK_HERE"&&localStorage.getItem("_MANUAL_MAPID")!==null)?localStorage.getItem("_MANUAL_MAPID"):manualSave;
goodGuesses =  (goodGuesses=="MAP_LINK_HERE"&& localStorage.getItem("_GOOD_MAPID")!==null)?localStorage.getItem("_GOOD_MAPID"):goodGuesses;
okGuesses =  (okGuesses=="MAP_LINK_HERE"&& localStorage.getItem("_OK_MAPID")!==null)?localStorage.getItem("_OK_MAPID"):okGuesses;
badGuesses =  (badGuesses=="MAP_LINK_HERE"&& localStorage.getItem("_BAD_MAPID")!==null)?localStorage.getItem("_BAD_MAPID"):badGuesses;
//try to read "save to map" preferences from local storage
collectGoodGuesses = (localStorage.getItem("_STORE_GOOD")!==null)?(localStorage.getItem("_STORE_GOOD")=="true"):collectGoodGuesses;
collectOkGuesses = (localStorage.getItem("_STORE_OK")!==null)?(localStorage.getItem("_STORE_OK")=="true"):collectOkGuesses;
collectBadGuesses = (localStorage.getItem("_STORE_BAD")!==null)?(localStorage.getItem("_STORE_BAD")=="true"):collectBadGuesses;
//Try to read good and ok cutoffs from localStorage
cutOffGood = (localStorage.getItem("_CUTOFF_GOOD")!==null)?Number(localStorage.getItem("_CUTOFF_GOOOD")):cutOffGood;
cutoffOk = (localStorage.getItem("_CUTOFF_OK")!==null)?Number(localStorage.getItem("_CUTOFF_OK")):cutoffOk;



const MAPS_PUBLISHED = "https://www.geoguessr.com/api/v3/profiles/maps?page=0&count=40";
const MAPS_DRAFT     = "https://www.geoguessr.com/api/v4/user-maps/dangling-drafts";
//Gets all published and draft messages from geo api.
var myMaps = {};
getUserMaps();


let global_loc;
let LOC_SAVE = "save loc";
 
if (sessionStorage.getItem("Streak") == null) {
    sessionStorage.setItem("Streak", 0);
};
if (sessionStorage.getItem("StreakBackup") == null) {
    sessionStorage.setItem("StreakBackup", 0);
};
if (sessionStorage.getItem("Checked") == null) {
    sessionStorage.setItem("Checked", 0);
};
 
let streak = parseInt(sessionStorage.getItem("Streak"), 10);
let last_guess = [0,0];
const ERROR_RESP = -1000000;
 
var CountryDict = {
    AF: 'AF',
    AX: 'FI', // Aland Islands
    AL: 'AL',
    DZ: 'DZ',
    AS: 'US', // American Samoa
    AD: 'AD',
    AO: 'AO',
    AI: 'GB', // Anguilla
    AQ: 'AQ', // Antarctica
    AG: 'AG',
    AR: 'AR',
    AM: 'AM',
    AW: 'NL', // Aruba
    AU: 'AU',
    AT: 'AT',
    AZ: 'AZ',
    BS: 'BS',
    BH: 'BH',
    BD: 'BD',
    BB: 'BB',
    BY: 'BY',
    BE: 'BE',
    BZ: 'BZ',
    BJ: 'BJ',
    BM: 'GB', // Bermuda
    BT: 'BT',
    BO: 'BO',
    BQ: 'NL', // Bonaire, Sint Eustatius, Saba
    BA: 'BA',
    BW: 'BW',
    BV: 'NO', // Bouvet Island
    BR: 'BR',
    IO: 'GB', // British Indian Ocean Territory
    BN: 'BN',
    BG: 'BG',
    BF: 'BF',
    BI: 'BI',
    KH: 'KH',
    CM: 'CM',
    CA: 'CA',
    CV: 'CV',
    KY: 'UK', // Cayman Islands
    CF: 'CF',
    TD: 'TD',
    CL: 'CL',
    CN: 'CN',
    CX: 'AU', // Christmas Islands
    CC: 'AU', // Cocos (Keeling) Islands
    CO: 'CO',
    KM: 'KM',
    CG: 'CG',
    CD: 'CD',
    CK: 'NZ', // Cook Islands
    CR: 'CR',
    CI: 'CI',
    HR: 'HR',
    CU: 'CU',
    CW: 'NL', // Curacao
    CY: 'CY',
    CZ: 'CZ',
    DK: 'DK',
    DJ: 'DJ',
    DM: 'DM',
    DO: 'DO',
    EC: 'EC',
    EG: 'EG',
    SV: 'SV',
    GQ: 'GQ',
    ER: 'ER',
    EE: 'EE',
    ET: 'ET',
    FK: 'GB', // Falkland Islands
    FO: 'DK', // Faroe Islands
    FJ: 'FJ',
    FI: 'FI',
    FR: 'FR',
    GF: 'FR', // French Guiana
    PF: 'FR', // French Polynesia
    TF: 'FR', // French Southern Territories
    GA: 'GA',
    GM: 'GM',
    GE: 'GE',
    DE: 'DE',
    GH: 'GH',
    GI: 'UK', // Gibraltar
    GR: 'GR',
    GL: 'DK', // Greenland
    GD: 'GD',
    GP: 'FR', // Guadeloupe
    GU: 'US', // Guam
    GT: 'GT',
    GG: 'GB', // Guernsey
    GN: 'GN',
    GW: 'GW',
    GY: 'GY',
    HT: 'HT',
    HM: 'AU', // Heard Island and McDonald Islands
    VA: 'VA',
    HN: 'HN',
    HK: 'CN', // Hong Kong
    HU: 'HU',
    IS: 'IS',
    IN: 'IN',
    ID: 'ID',
    IR: 'IR',
    IQ: 'IQ',
    IE: 'IE',
    IM: 'GB', // Isle of Man
    IL: 'IL',
    IT: 'IT',
    JM: 'JM',
    JP: 'JP',
    JE: 'GB', // Jersey
    JO: 'JO',
    KZ: 'KZ',
    KE: 'KE',
    KI: 'KI',
    KR: 'KR',
    KW: 'KW',
    KG: 'KG',
    LA: 'LA',
    LV: 'LV',
    LB: 'LB',
    LS: 'LS',
    LR: 'LR',
    LY: 'LY',
    LI: 'LI',
    LT: 'LT',
    LU: 'LU',
    MO: 'CN', // Macao
    MK: 'MK',
    MG: 'MG',
    MW: 'MW',
    MY: 'MY',
    MV: 'MV',
    ML: 'ML',
    MT: 'MT',
    MH: 'MH',
    MQ: 'FR', // Martinique
    MR: 'MR',
    MU: 'MU',
    YT: 'FR', // Mayotte
    MX: 'MX',
    FM: 'FM',
    MD: 'MD',
    MC: 'MC',
    MN: 'MN',
    ME: 'ME',
    MS: 'GB', // Montserrat
    MA: 'MA',
    MZ: 'MZ',
    MM: 'MM',
    NA: 'NA',
    NR: 'NR',
    NP: 'NP',
    NL: 'NL',
    AN: 'NL', // Netherlands Antilles
    NC: 'FR', // New Caledonia
    NZ: 'NZ',
    NI: 'NI',
    NE: 'NE',
    NG: 'NG',
    NU: 'NZ', // Niue
    NF: 'AU', // Norfolk Island
    MP: 'US', // Northern Mariana Islands
    NO: 'NO',
    OM: 'OM',
    PK: 'PK',
    PW: 'PW',
    PS: 'IL', // Palestine
    PA: 'PA',
    PG: 'PG',
    PY: 'PY',
    PE: 'PE',
    PH: 'PH',
    PN: 'GB', // Pitcairn
    PL: 'PL',
    PT: 'PT',
    PR: 'US', // Puerto Rico
    QA: 'QA',
    RE: 'FR', // Reunion
    RO: 'RO',
    RU: 'RU',
    RW: 'RW',
    BL: 'FR', // Saint Barthelemy
    SH: 'GB', // Saint Helena
    KN: 'KN',
    LC: 'LC',
    MF: 'FR', // Saint Martin
    PM: 'FR', // Saint Pierre and Miquelon
    VC: 'VC',
    WS: 'WS',
    SM: 'SM',
    ST: 'ST',
    SA: 'SA',
    SN: 'SN',
    RS: 'RS',
    SC: 'SC',
    SL: 'SL',
    SG: 'SG',
    SX: 'NL', // Sint Maarten
    SK: 'SK',
    SI: 'SI',
    SB: 'SB',
    SO: 'SO',
    ZA: 'ZA',
    GS: 'GB', // South Georgia and the South Sandwich Islands
    ES: 'ES',
    LK: 'LK',
    SD: 'SD',
    SR: 'SR',
    SJ: 'NO', // Svalbard and Jan Mayen
    SZ: 'SZ',
    SE: 'SE',
    CH: 'CH',
    SY: 'SY',
    TW: 'TW', // Taiwan
    TJ: 'TJ',
    TZ: 'TZ',
    TH: 'TH',
    TL: 'TL',
    TG: 'TG',
    TK: 'NZ', // Tokelau
    TO: 'TO',
    TT: 'TT',
    TN: 'TN',
    TR: 'TR',
    TM: 'TM',
    TC: 'GB', // Turcs and Caicos Islands
    TV: 'TV',
    UG: 'UG',
    UA: 'UA',
    AE: 'AE',
    GB: 'GB',
    US: 'US',
    UM: 'US', // US Minor Outlying Islands
    UY: 'UY',
    UZ: 'UZ',
    VU: 'VU',
    VE: 'VE',
    VN: 'VN',
    VG: 'GB', // British Virgin Islands
    VI: 'US', // US Virgin Islands
    WF: 'FR', // Wallis and Futuna
    EH: 'MA', // Western Sahara
    YE: 'YE',
    ZM: 'ZM',
    ZW: 'ZW'
};
 
function hex2a(hexx) {
    var hex = hexx.toString();
    var str = '';
    for (var i = 0; i < hex.length; i += 2)
    {
        str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
    }
    return str;
}
 
 
if (AUTOMATIC && (API_Key.length <= 24 || API_Key.match("^[a-fA-F0-9_]*$") == null)) {
    AUTOMATIC = false;
};
 
function checkGameMode() {
    return (location.pathname.startsWith("/game/") || (ENABLED_ON_CHALLENGES && location.pathname.startsWith("/challenge/")));
};
 
let _cndic = {};
function cn(classNameStart) { // cn("status_section__") -> "status_section__8uP8o"
    let memorized = _cndic[classNameStart];
    if (memorized != null) return memorized;
    let selected = document.querySelector(`div[class*="${classNameStart}"]`);
    if (selected == null) return classNameStart;
    for (let className of selected.classList) {
        if (className.startsWith(classNameStart)) {
            _cndic[classNameStart] = className;
            return className;
        }
    }
}

function geoguessrStyle(number) {
    return `<div class="${cn("guess-description-distance_distanceLabel__")}">
                <div class="${cn("slanted-wrapper_root__")} ${cn("slanted-wrapper_variantWhiteTransparent__")} ${cn("slanted-wrapper_roundnessSmall__")}">
                    <div class="${cn("slanted-wrapper_start__")} ${cn("slanted-wrapper_right__")}"></div>
                    <div class="${cn("guess-description-distance_distanceValue__")}">${number}</div>
                    <div class="${cn("slanted-wrapper_end__")} ${cn("slanted-wrapper_right__")}"></div>
                </div>
            </div>`;
};
/**
 * Opens a settings modal with the following functionality
 * 
 * 1) Add or change the API Key.
 * 2) Enable or disable saving good, ok, and bad guesses.
 * 3) Choose a map to save to from a dropdown for manual, good, ok, and bad.
 * The preferences are saved to localStorage, meaning they are persistent and are remembered upon reload or restart.
 * The settings window is accessed by clicking on the current streak indicator and by clicking a button on the round summary and game overview. 
 */
function openSettings(){
    if(document.getElementById("streaks-settings-container")!=null){
        document.getElementById("streaks-settings-container").style.display="flex";
    }else{
    let containerCSS = `position:absolute; display:flex; justify-content:center; align-items:center; z-index:1; width:100%; height:100%;`;
    let modalCSS=`background-color: #1e1d56; color:#fafafa; display:flex; flex-direction:column; width:100vmin;`;
    let modalContentCSS= `display:flex;flex-direction:column;justify-content:center;`;
    let headingCSS =  `text-align:center; font-weight:bold;`;
    let spanStyle = `display: flex; color: #aaa; float: right; font-size: 28px; margin-right: 1vmin;font-weight: bold; justify-content: flex-end;cursor:pointer;`;
    let bodyCSS= ` display:flex; flex-direction:column;`;
    let html = 
    `
    <style>
    .help-tip{
        position: absolute;
        top: -5px;
        right: -28px;
        text-align: center;
        background-color: #BCDBEA;
        border-radius: 50%;
        width: 24px;
        height: 24px;
        font-size: 14px;
        line-height: 26px;
        cursor: default;
    }
    
    .help-tip:before{
        content:'?';
        font-weight: bold;
        color:#222;
    }
    
    .help-tip:hover p{
        display:block;
        transform-origin: 100% 0%;
    
        -webkit-animation: fadeIn1 0.3s ease-in-out;
        animation: fadeIn1 0.3s ease-in-out;
    
    }
    
    .help-tip p{    /* The tooltip */
        display: none;
        text-align: left;
        background-color: #1E2021;
        padding: 20px;
        width: 300px;
        position: absolute;
        border-radius: 3px;
        box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.2);
        right: -4px;
        color: #FFF;
        font-size: 13px;
        line-height: 1.4;
    }
    
    .help-tip p:before{ /* The pointer of the tooltip */
        position: absolute;
        content: '';
        width:0;
        height: 0;
        border:6px solid transparent;
        border-bottom-color:#1E2021;
        right:10px;
        top:-12px;
    }
    
    .help-tip p:after{ /* Prevents the tooltip from being hidden */
        width:100%;
        height:40px;
        content:'';
        position: absolute;
        top:-40px;
        left:0;
    }
    
    /* CSS animation */
    
    @-webkit-keyframes fadeIn1 {
        0% { 
            opacity:0; 
            transform: scale(0.6);
        }
    
        100% {
            opacity:100%;
            transform: scale(1);
        }
    }
    .selectTooltip{
        position:relative;
    }
    @keyframes fadeIn1 {
        0% { opacity:0; }
        100% { opacity:100%; }
    }
    </style>
    <div style="${modalCSS}" id="streaks-settings">
        <span style="${spanStyle}" id="close-modal">&times;</span>
        <div style="${modalContentCSS}">
            <div style="${headingCSS}">
                <h2>Country Streaks</h2>
                <h5>Created by Jupaoqq</h5>
                <h5>Credits to victheturtle, subsymmetry, slashP, emilyapocalypse, and Han75</h5>
            </div>
            <div style="${bodyCSS}">
                <div style="border:5px groove #0F0F0F; padding:5px;">
                    <label for="streak-API-key">Your API Key:</label>
                    <input type="password" style="padding:5px; float:right;" id="streak-API-key">
                    <br><br>
                    <button style="float:right;cursor:pointer;" id="streak-setkey">Set API Key</button>
                    <a style="float:right;text-decoration:underline;color:white;cursor:pointer;"id="showKey">Show</a>
                    <p id="status-text"></p>
                    <p>
                    Need a new API key? Make an account at <a style="text-decoration:underline;color:blue;" href = "https://www.bigdatacloud.com/" target="_blank">www.bigdatacloud.com</a>
                    </p>
                </div>
                <div style="border:5px groove #1F1F1F; padding:5px;">
                    <label class="selectTooltip" for="selectManual">Manually save locations to this map: </label> 
                    <select style="float:right;" id="selectManual">
                        <option value="MAP_LINK_HERE" selected>No Selection</option>
                    </select>
                    <br><br>
                    <label for="changeKeybind">Manual Save Keybind: </label>
                    <input type="text" style="float:right;padding:5px;width:50px;" id = "changeKeybind" value="${manualKey}" maxlength="1" >
                    <br><br>
                    <button style="float:right;cursor:pointer;"id="savePrefManual">Save Preferences</button>
                </div>
                <div style="border:5px groove #0F0F0F; padding:5px;">
                    <label class="selectTooltip" for="selectGood">Save <b>GOOD</b> guesses to this map:<div class="help-tip">
                    <p>A guess is counted as a good guess if the country is correct and the score is <i>higher</i> than the minimum score.</p>
                </div></label>
                    
                    <select style="float:right;" id="selectGood">
                        <option value="MAP_LINK_HERE" selected>No Selection</option>
                    </select>
                    <br><br>
                    <label for="goodThreshhold">Good guess minimum score:</label>
                    <input id="goodThreshhold" style="float:right;padding:5px;width:70px;" type="number" max="4999" value="${cutOffGood}">
                    <br><br>
                    <label for="toggleGood">Save <b>GOOD</b> guesses?</label>
                    <input type="checkbox" id="toggleGood" ${collectGoodGuesses?"checked":""}>
                    <button style="float:right;cursor:pointer;" id="savePrefGood">Save Preferences</button>
                </div>
                <div style="border:5px groove #0F0F0F; padding:5px;">
                    <label class="selectTooltip" for="selectOK">Save <b>OK</b> guesses to this map: <div class="help-tip">
                    <p>A guess is counted as an OK guess if the country is correct and the score is <i>lower</i> than the maximum score.</p>
                </div></label>
                    
                    <select style="float:right;" id="selectOK">
                        <option value="MAP_LINK_HERE" selected>No Selection</option>
                    </select>
                    <br><br>
                    <label for="okThreshhold"><b>OK</b> guess maximum score:</label>
                    <input id="okThreshhold" style="float:right;padding:5px;width:70px;" type="number" max="4999" value="${cutoffOk}">
                    <br><br>
                    <label for="toggleOk">Save <b>OK</b> guesses?</label>
                    <input type="checkbox" id="toggleOk" ${collectOkGuesses?"checked":""}>
                    <button style="float:right" id="savePrefOk">Save Preferences</button>
                </div>
                <div style="border:5px groove #0F0F0F; padding:5px;">
                    <label class="selectTooltip" for="selectBad">Save <b>BAD</b> guesses (incorrect country) to this map:<div class="help-tip">
                    <p>A guess is counted as a bad guess if the country is incorrect.</p>
                </div></label>
                    
                    <select style="float:right;" id="selectBad">
                        <option value="MAP_LINK_HERE" selected>No Selection</option>
                    </select>
                    <br><br>
                    <label for="toggleBad">Save <b>BAD</b> guesses?</label>
                    <input type="checkbox" id="toggleBad" ${collectBadGuesses?"checked":""}>
                    <button style="float:right;cursor:pointer;" id="savePrefBad">Save Preferences</button>
                </div>
            </div>
        </div>
    </div>
    `;
    //Put modal in main body container
    let main = document.getElementById("__next");
    let settingsDiv = document.createElement("div");
    settingsDiv.id="streaks-settings-container";
    settingsDiv.innerHTML=html;
    settingsDiv.style.cssText=containerCSS;
    main.insertBefore(settingsDiv,main.children[0]);
    //close button
    document.getElementById("showKey").addEventListener("click",(e)=>{
        keyInput=document.getElementById("streak-API-key");
        if(keyInput.type==="password"){
            keyInput.type="text";
            document.getElementById("showKey").innerText="Hide";
        }else{
            keyInput.type="password";
            document.getElementById("showKey").innerText="Show";
        }
    });
    document.getElementById("close-modal").addEventListener("click",(e)=>{
        document.getElementById("streaks-settings-container").style.display="none";
    });
    document.getElementById("")
    //Populate dropdown select boxes.
    let ids=["selectManual","selectGood","selectOK","selectBad"]
    for(const [mapID, mapName] of Object.entries(myMaps)){
        
        for(const id of ids){
            let option = document.createElement("option");
            option.value=mapID;
            option.innerText=mapName;
            document.getElementById(id).appendChild(option);
        }
    }
    //set api key button
    document.getElementById("streak-setkey").addEventListener("click",(e)=>{
        let tryKey = document.getElementById("streak-API-key").value;
        if(!(tryKey.length <= 24 || tryKey.match("^[a-fA-F0-9_]*$") == null)){
            API_Key=tryKey;
            AUTOMATIC=true;
            localStorage.setItem("_STREAK_API_KEY",API_Key);
            document.getElementById("status-text").innerText="Successfully set key.";
        }else{
            document.getElementById("status-text").innerText="The key you entered is invalid. Please try again."
        }
        setTimeout(function(){
            document.getElementById("status-text").innerText="";
        },5000)
    });
    //manual preferences button
    document.getElementById("savePrefManual").addEventListener("click",(e)=>{
        manualSave = document.getElementById("selectManual").value;
        manualKey = document.getElementById("changeKeybind").value;
        localStorage.setItem("_MANUAL_MAPID",manualSave);
        document.getElementById("savePrefManual").innerText = "Saved.";
        setTimeout(function(){
            document.getElementById("savePrefManual").innerText = "Save Preferences";
        },3000);
    });
    //good preferences button
    document.getElementById("savePrefGood").addEventListener("click",(e)=>{
        goodGuesses = document.getElementById("selectGood").value;
        localStorage.setItem("_GOOD_MAPID",goodGuesses);
        if(goodGuesses=="MAP_LINK_HERE"){
            document.getElementById("toggleGood").checked=false;
        }
        cutOffGood = document.getElementById("goodThreshhold").value;
        localStorage.setItem("_CUTOFF_GOOD",cutOffGood);
        collectGoodGuesses = document.getElementById("toggleGood").checked;
        localStorage.setItem("_STORE_GOOD",collectGoodGuesses.toString());
        document.getElementById("savePrefGood").innerText = "Saved.";
        setTimeout(function(){
            document.getElementById("savePrefGood").innerText = "Save Preferences";
        },3000);
    });
    //ok preferences button
    document.getElementById("savePrefOk").addEventListener("click",(e)=>{
        okGuesses = document.getElementById("selectOK").value;
        localStorage.setItem("_OK_MAPID",okGuesses);
        if(okGuesses=="MAP_LINK_HERE"){
            document.getElementById("toggleOk").checked=false;
        }
        cutoffOk = document.getElementById("okThreshhold").value;
        localStorage.setItem("_CUTOFF_OK",cutoffOk);
        collectOkGuesses = document.getElementById("toggleOk").checked;
        localStorage.setItem("_STORE_OK",collectOkGuesses.toString());
        document.getElementById("savePrefOk").innerText = "Saved.";
        setTimeout(function(){
            document.getElementById("savePrefOk").innerText = "Save Preferences";
        },3000);
    });
    //bad preferences button
    document.getElementById("savePrefBad").addEventListener("click",(e)=>{
        badGuesses = document.getElementById("selectBad").value;
        localStorage.setItem("_BAD_MAPID",badGuesses);
        if(badGuesses=="MAP_LINK_HERE"){
            document.getElementById("toggleBad").checked=false;
        }
        collectBadGuesses = document.getElementById("toggleBad").checked;
        localStorage.setItem("_STORE_BAD",collectBadGuesses.toString())
        document.getElementById("savePrefBad").innerText = "Saved.";
        setTimeout(function(){
            document.getElementById("savePrefBad").innerText = "Save Preferences";
        },3000);
    });
    }
    //Every time settings opened:
    //Populate api key box
    document.getElementById("streak-API-key").value=API_Key;
    //populate manual select box
    document.getElementById("selectManual").value=manualSave;
    document.getElementById("changeKeybind").value=manualKey;
    //Populate good select box
    document.getElementById("selectGood").value=goodGuesses;
    document.getElementById("goodThreshhold").value=cutOffGood;
    document.getElementById("toggleGood").checked=collectGoodGuesses;
    //populate ok select box
    document.getElementById("selectOK").value=okGuesses;
    document.getElementById("okThreshhold").value=cutoffOk;
    document.getElementById("toggleOk").checked=collectOkGuesses;
    //populate bad select box
    document.getElementById("selectBad").value=badGuesses;
    document.getElementById("toggleBad").checked=collectBadGuesses;
}
function addCounter() {
    if (!checkGameMode()) {
        return;
    };
    let status_length = document.getElementsByClassName(cn("status_section__")).length;
    if (document.getElementById("country-streak") == null && status_length >= 3) {
        let position = (status_length >= 4 && document.getElementsByClassName(cn("status_label__"))[3].innerText == "TIME LEFT") ? 4 : 3;
        let newDiv0 = document.createElement("div");
        newDiv0.className = cn('status_section__');
        let statusBar = document.getElementsByClassName(cn("status_inner__"))[0];
        statusBar.insertBefore(newDiv0, statusBar.children[position]);
        newDiv0.innerHTML = `<div class="${cn("status_label__")}">Streak</div>
                             <div id="country-streak" class="${cn("status_value__")}">${streak}</div>`;
        newDiv0.setAttribute("style","cursor:pointer;");
        newDiv0.addEventListener("click",openSettings);
    };
};
 
function addStreakRoundResult() {
    if (document.getElementById("country-streak2") == null && !!document.querySelector('div[data-qa="guess-description"]')
        && !document.querySelector('div[class*="standard-final-result_section__"]')) {
        let pageProps = JSON.parse(document.getElementById("__NEXT_DATA__").innerHTML).props.pageProps;
        if (pageProps.gamePlayedByCurrentUser != null && pageProps.gamePlayedByCurrentUser.mode == "streak") return;
        let newDiv = document.createElement("div");
        document.querySelector('div[data-qa="guess-description"]').appendChild(newDiv);
        newDiv.innerHTML = `<div id="country-streak2" style="text-align:center;margin-top:10px;"><h2><i>Country Streak: ${streak}</i></h2></div>`;
        let openS = document.createElement("h4");
        openS.innerText="Open Streak Counter Preferences";
        newDiv.insertBefore(openS,null);
        openS.setAttribute("style","text-decoration:underline;cursor:pointer;");
        openS.addEventListener("mouseover",function(){
            openS.setAttribute("style","color:blue;text-decoration:underline;cursor:pointer;");
        });
        openS.addEventListener("mouseout",function(){
            openS.setAttribute("style","color:white;text-decoration:underline;cursor:pointer;");
        });
        openS.addEventListener("click",openSettings);
    };
};
 
function addStreakGameSummary() {
    if (document.getElementById("country-streak2") == null && !!document.querySelector('div[class*="standard-final-result_section__"]')) {
        let newDiv = document.createElement("div");
        let progressSection = document.getElementsByClassName(cn("standard-final-result_progressSection__"))[0];
        progressSection.parentNode.insertBefore(newDiv, progressSection.parentNode.children[2]);
        progressSection.style.marginTop = "10px";
        progressSection.style.marginBottom = "10px";
        newDiv.innerHTML = `<div id="country-streak2" style="text-align:center;margin-top:10px;"><h2><i>Country Streak: ${streak}</i></h2></div>`;
        let openS = document.createElement("h4");
        openS.innerText="Open Streak Counter Preferences";
        newDiv.insertBefore(openS,null);
        openS.setAttribute("style","text-decoration:underline;cursor:pointer;");
        openS.addEventListener("mouseover",function(){
            openS.setAttribute("style","color:blue;text-decoration:underline;cursor:pointer;");
        });
        openS.addEventListener("mouseout",function(){
            openS.setAttribute("style","color:white;text-decoration:underline;cursor:pointer;");
        });
        openS.addEventListener("click",openSettings);
    };
};
 
function updateStreak(newStreak, cond, guessType) {
    if (newStreak === LOC_SAVE) {
        if (document.getElementById("country-streak2") != null && (!!document.querySelector('div[data-qa="guess-description"]'))) {
            document.getElementById("country-streak2").innerHTML = SaveText;
        }
        return;
    }
    else if (newStreak === ERROR_RESP) {
        if (document.getElementById("country-streak2") != null && (!!document.querySelector('div[data-qa="guess-description"]'))) {
            document.getElementById("country-streak2").innerHTML =
                `<div><i>Country codes could not be fetched. If your API key is new, it should activate soon.</i></div>
                 <div><i>Check for typos in the API key. You might also see this message if bigdatacloud is down</i></div>
                 <div><i>or in the unlikely event that you have exceeded you quota limit of 50,000 requests.</i></div>
                 <div><i>In the meantime, you can press 1 to count the country as correct, or press 0 otherwise.</i></div>`;
        }
        return;
    }
    sessionStorage.setItem("Streak", newStreak);
    if (!(streak > 0 && newStreak == 0)) {
        sessionStorage.setItem("StreakBackup", newStreak);
    };
    if (document.getElementById("country-streak") != null) {
        document.getElementById("country-streak").innerHTML = newStreak;
    };
    if (document.getElementById("country-streak2") != null
        && (!!document.querySelector('div[data-qa="guess-description"]') || !!document.querySelector('div[class*="standard-final-result_section__"]'))) {
 
        let moreText1 = "";
        let moreText2 = "";
        if (collectGoodGuesses && guessType === "PERFECT")
        {
            moreText1 = GoodText;
        }
 
        else if (collectOkGuesses && guessType === "BAD")
        {
            moreText1 = OkText;
        }
 
        if (collectBadGuesses && guessType === "MISS")
        {
            moreText2 = BadText;
        }
 
        if (manualSave !== "MAP_LINK_HERE")
        {
            defaultText = `You may press the ${manualKey} key on your keyboard to save this location.`
        }
 
        document.getElementById("country-streak2").innerHTML = `<h2><i>Country Streak: ${newStreak}</i></h2> <br> ${defaultText} <br> ${moreText1}`;
        if (newStreak == 0 && !cond) {
            if (streak >= 2) {
                document.getElementById("country-streak2").innerHTML = `<h2><i>Country Streak: 0</i></h2>
                    Your streak ended after correctly guessing ${geoguessrStyle(streak)} countries in a row. <br> ${defaultText} <br> ${moreText2}`;
            } else if (streak == 1) {
                document.getElementById("country-streak2").innerHTML = `<h2><i>Country Streak: 0</i></h2>
                    Your streak ended after correctly guessing ${geoguessrStyle(1)} country. <br> ${defaultText} <br> ${moreText2}`;
            }
            else {
                document.getElementById("country-streak2").innerHTML = `<br><h2><i>Country Streak: 0</i></h2>
                    Your streak ended after correctly guessing ${geoguessrStyle(0)} country. <br> ${defaultText} <br> ${moreText2}`;
            };
        };
    };
    streak = newStreak;
};
/**
 * Gets user's draft and published maps and stores (Map_ID,Map_Name) in variable myMaps 
 */
async function getUserMaps(){
    await fetch(MAPS_PUBLISHED).then(res=>(res.status!==200)?ERROR_RESP:res.json()).then(json=>{
        if(json!==ERROR_RESP){
            for(let i=0;i<json.length;i++){
                myMaps[json[i]["slug"]]=json[i]["name"];
            }
        }else console.log("error getting user published maps from geoguessr api");
    });
    await fetch(MAPS_DRAFT).then(res=>(res.status!==200)?ERROR_RESP:res.json()).then(json=>{
        if(json!==ERROR_RESP){
            for(let i=0;i<json.length;i++){
                myMaps[json[i]["slug"]]=json[i]["name"];
            }
        }else console.log("error getting user draft maps from geoguessr api");
    });

}
async function getUserAsync(coords) {
    if (coords[0] <= -85.05) {
        return 'AQ';
    };
    let api = "https://api.bigdatacloud.net/data/reverse-geocode?latitude="+coords[0]+"&longitude="+coords[1]+"&localityLanguage=en&key="+API_Key
    let response = await fetch(api)
    .then(res => (res.status !== 200) ? ERROR_RESP : res.json())
    .then(out => (out === ERROR_RESP) ? ERROR_RESP : CountryDict[out.countryCode]);
    return response;
};
 
function check() {
    const game_tag = window.location.href.substring(window.location.href.lastIndexOf('/') + 1)
    let api_url = ""
    if (location.pathname.startsWith("/game/")) {
        api_url = "https://www.geoguessr.com/api/v3/games/"+game_tag;
    } else if (location.pathname.startsWith("/challenge/")) {
        api_url = "https://www.geoguessr.com/api/v3/challenges/"+game_tag+"/game";
    };
    fetch(api_url)
        .then(res => res.json())
        .then((out) => {
        let guess_counter = out.player.guesses.length;
        let guess = [out.player.guesses[guess_counter-1].lat,out.player.guesses[guess_counter-1].lng];
        if (guess[0] == last_guess[0] && guess[1] == last_guess[1]) {
            return;
        };
        last_guess = guess;
        let round = [out.rounds[guess_counter-1].lat,out.rounds[guess_counter-1].lng];
        global_loc = out.rounds[guess_counter-1];
        getUserAsync(guess)
            .then(gue => {
            getUserAsync(round)
                .then(loc => {
                if (loc == ERROR_RESP || gue == ERROR_RESP) {
                    updateStreak(ERROR_RESP, true, "");
                } else if (loc == gue) {
                    let passStr = "";
                    if (out.player.guesses[guess_counter-1].roundScore.amount < cutoffOk)
                    {
                        if (collectOkGuesses && okGuesses !== "MAP_LINK_HERE")
                        {
                            toMap(global_loc, "BAD");
                            passStr = "BAD";
                        }
                    }
                    if (out.player.guesses[guess_counter-1].roundScore.amount > cutOffGood)
                    {
                        if (collectGoodGuesses && goodGuesses !== "MAP_LINK_HERE")
                        {
                            toMap(global_loc, "PERFECT");
                            passStr = "PERFECT";
                        }
                    }
                    updateStreak(streak + 1, true, passStr);
                } else {
                    updateStreak(0, false, "MISS");
                    if (collectBadGuesses && badGuesses !== "MAP_LINK_HERE")
                    {
                        toMap(global_loc, "MISS");
                    }
                };
            });
        });
    }).catch(err => { throw err });
};
 
function doCheck() {
    if (!document.querySelector('div[class*="result-layout_root__"]')) {
        sessionStorage.setItem("Checked", 0);
    } else if (sessionStorage.getItem("Checked") == 0) {
        check();
        sessionStorage.setItem("Checked", 1);
    }
};
 
function tryAddCounter() {
    addCounter();
    for (let timeout of [400,1200,2000,3000,4000]) {
        if (document.getElementsByClassName(cn("status_section__")).length == 0) {
            setTimeout(addCounter, timeout);
        };
    }
};
 
function tryAddCounterOnRefresh() {
    setTimeout(addCounter, 50);
    setTimeout(addCounter, 300);
};
 
function tryAddStreak() {
    if (!checkGameMode()) {
        return;
    };
    if (AUTOMATIC) {
        doCheck();
        for (let timeout of [250,500,1200,2000]) {
            setTimeout(doCheck, timeout);
        }
    };
    for (let timeout of [250,500,1200,2000]) {
        setTimeout(addStreakRoundResult, timeout);
        setTimeout(addStreakGameSummary, timeout);
    }
};

document.addEventListener('keypress', (e) => {
    let streakBackup = parseInt(sessionStorage.getItem("StreakBackup"), 10);
    switch (e.key) {
        case '1':
            updateStreak(streak + 1, true, "");
            break;
        case '2':
            updateStreak(streak - 1, true, "");
            break;
        case '8':
            updateStreak(streakBackup + 1, true, "");
            break;
        case manualKey:
            toMap(global_loc, "SAVE");
            updateStreak(LOC_SAVE, true, "");
            break;
        case '0':
            updateStreak(0, true, "");
            sessionStorage.setItem("StreakBackup", 0);
    };
});
 
document.addEventListener('click', tryAddCounter, false);
document.addEventListener('click', tryAddStreak, false);
document.addEventListener('keyup', (e) => { if (e.key === " ") { tryAddStreak(); } });
document.addEventListener('load', tryAddCounterOnRefresh(), false);
 
function toMap(loc, type)
{
    let coordinates = [];
    let pId;
    if (loc.panoId)
    {
        pId = hex2a(loc.panoId);
    }
    const coordinate = {
        heading: loc.heading,
        pitch: loc.pitch,
        zoom:  loc.zoom,
        panoId: pId,
        countryCode: loc.streakLocationCode || null,
        stateCode: null,
        lat: loc.lat,
        lng: loc.lng
    };
    coordinates.push(coordinate);
 
 
    const mapText = JSON.stringify({
        customCoordinates: coordinates
    });
    importLocations(mapText, type);
}
 
let mapDataFromClipboard = null;
let existingMap = null;
 
const getExistingMapData = (type) => {
    let mId;
    if (type == "PERFECT")
    {
        mId = goodGuesses;
    }
    else if (type == "BAD")
    {
        mId = okGuesses;
    }
    else if (type == "MISS")
    {
        mId = badGuesses;
    }
    else if (type == "SAVE")
    {
        mId = manualSave;
    }
    return fetch(`https://www.geoguessr.com/api/v3/profiles/maps/${mId}`)
        .then(response => response.json())
        .then(map => ({
        id: map.id,
        name: map.name,
        description: map.description,
        avatar: map.avatar,
        highlighted: map.highlighted,
        published: map.published,
        customCoordinates: map.customCoordinates
    }));
}
const uniqueBy = (arr, selector) => {
    const flags = {};
    return arr.filter(entry => {
        if (flags[selector(entry)]) {
            return false;
        }
        flags[selector(entry)] = true;
        return true;
    });
};
const intersectionCount = (arr1, arr2, selector) => {
    var setB = new Set(arr2.map(selector));
    var intersection = arr1.map(selector).filter(x => setB.has(x));
    return intersection.length;
}
const exceptCount = (arr1, arr2, selector) => {
    var setB = new Set(arr2.map(selector));
    var except = arr1.map(selector).filter(x => !setB.has(x));
    return except.length;
}
const latLngSelector = x => `${x.lat},${x.lng}`;
const latLngHeadingPitchSelector = x => `${x.lat},${x.lng},${x.heading},${x.pitch}`;
const pluralize = (text, count) => count === 1 ? text : text + "s";
 
const importLocations = (text, type, mapAsObject) => {
    try {
        getExistingMapData(type)
            .then(map => {
            existingMap = map;
            mapDataFromClipboard = mapAsObject ? mapAsObject : JSON.parse(text);
            if (!mapDataFromClipboard?.customCoordinates?.length) {
                return;
            }
            const uniqueExistingLocations = uniqueBy(existingMap.customCoordinates, latLngSelector);
            const uniqueImportedLocations = uniqueBy(mapDataFromClipboard.customCoordinates, latLngSelector);
            const uniqueLocations = uniqueBy([...uniqueExistingLocations, ...uniqueImportedLocations], latLngSelector);
            const numberOfLocationsBeingAdded = uniqueLocations.length - uniqueExistingLocations.length;
            const numberOfUniqueLocationsImported = uniqueImportedLocations.length;
            const numberOfExactlyMatchingLocations = intersectionCount(uniqueExistingLocations, uniqueImportedLocations, latLngHeadingPitchSelector);
            const numberOfLocationsWithSameLatLng = intersectionCount(uniqueExistingLocations, uniqueImportedLocations, latLngSelector);
            const numberOfLocationEditions = numberOfLocationsWithSameLatLng - numberOfExactlyMatchingLocations;
            const numberOfLocationsNotInImportedList = exceptCount(uniqueExistingLocations, uniqueImportedLocations, latLngSelector);
            const numberOfLocationsNotInExistingMap = exceptCount(uniqueImportedLocations, uniqueExistingLocations, latLngSelector);
 
            const uniqueLocations2 = uniqueBy([...existingMap.customCoordinates, ...mapDataFromClipboard.customCoordinates], latLngSelector);
            const newMap = {
                ...existingMap,
                customCoordinates: uniqueLocations2
            };
            updateMap(newMap);
 
        }).catch(error => console.log(error));
    } catch (err) {
        console.log(err);
    }
}
 
 
function updateMap(newMap) {
    fetch(`https://www.geoguessr.com/api/v4/user-maps/drafts/${existingMap.id}`, {
        method: 'PUT',
        credentials: 'same-origin',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(newMap)
    }).then(response => {
        if (!response.ok) {
            console.log("Something went wrong when calling the server.");
            return;
        }
        return response.json();
    }).then(mapResponse => {
        if (mapResponse.id) {
            console.log(`Map updated.`);
        }
    });
        fetch(`https://www.geoguessr.com/api/v3/profiles/maps/${existingMap.id}`, {
        method: 'POST',
        credentials: 'same-origin',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(newMap)
    }).then(response => {
        if (!response.ok) {
            console.log("Something went wrong when calling the server.");
            return;
        }
        return response.json();
    }).then(mapResponse => {
        if (mapResponse.id) {
            console.log(`Map updated.`);
        }
    });
}