Geoguessr Activities Analysis Tool

map visualization of your geoguessr activities

当前为 2024-05-18 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Geoguessr Activities Analysis Tool
// @version      1.2
// @description  map visualization of your geoguessr activities
// @author       KaKa
// @match        https://map-making.app/
// @require      https://cdn.jsdelivr.net/npm/sweetalert2@11
// @license      MIT
// @icon         https://www.google.com/s2/favicons?domain=geoguessr.com
// @grant        GM_xmlhttpRequest
// @namespace http://tampermonkey.net/
// ==/UserScript==
(function() {
    'use strict';
    var u = 'https://www.geoguessr.com/api/v4/feed/friends?count=26';
    var map,heatmapLayer,streetViewContainer,streetViewMap
    let myNick
    let activities =JSON.parse(localStorage.getItem('activities'));
    let isUpdated=JSON.parse(localStorage.getItem('isUpdated'));
    getMyNick().then(nick => {
        myNick = nick;
    })
    .catch(error => {
        console.error("Failed to fetch user nick:", error);
    });
    if (!activities){
        activities={'duels':{},'games':{}};
    }

    function getMyNick() {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: "https://geoguessr.com/api/v3/profiles",
                onload: function(response) {
                    if (response.status === 200) {
                        const data = JSON.parse(response.responseText);
                        const myId=data.user.id
                        const nickMap={}
                        nickMap[myId]=data.user.nick
                        resolve(nickMap);
                    } else {
                        console.error("Error fetching user data: " + response.statusText);
                        reject(null);
                    }
                },
                onerror: function(error) {
                    console.error("Error fetching user data:", error);
                    reject(null);
                }
            });
        });
    }

    function matchPlayerNick(gameData, friendMap) {
        gameData.forEach(game => {
            const playerId = game.playerId;
            Object.assign(friendMap,myNick)
            game.playerNick = friendMap[playerId] || "Opponents";
        });
        return gameData;
    }

    function getFriends() {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: "https://www.geoguessr.com/api/v3/social/friends/summary?page=0&fast=true",
                onload: function(response) {
                    if (response.status === 200) {
                        try {
                            const data = JSON.parse(response.responseText);
                            const friendMap = {};
                            data.friends.forEach(friend => {
                                friendMap[friend.userId] = friend.nick;
                            });
                            resolve(friendMap);
                        } catch (error) {
                            console.error("Error parsing JSON: ", error);
                            reject(null);
                        }
                    } else {
                        console.error("Error fetching user data: " + response.statusText);
                        reject(null);
                    }
                },
                onerror: function(error) {
                    console.error("Error fetching user data:", error);
                    reject(null);
                }
            });
        });
    }

    function getDiffdays(dataList) {
        const currentDate = new Date();
        return dataList.map(item => {
            if ('gameDate' in item) {
                const gameDate = new Date(item.gameDate);
                const timeDiff = Math.abs(currentDate - gameDate);
                const daysDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
                item.gameDate = daysDiff;
            }
            return item;
        });
    }

    function saveLocalStorage(item) {
        localStorage.setItem('activities', JSON.stringify(item));
        activities =JSON.parse(localStorage.getItem('activities'));
        localStorage.setItem('isUpdated',JSON.stringify('updated'));

    }

    function downloadJSON(coords) {
        var convertedData = {
            "name": "coordinates",
           "customCoordinates": coords.map(function(item) {
            // Categorize score
            var scoreCategory;
            if (item.score >= 0 && item.score <= 1499) {
                scoreCategory = "0-1499";
            } else if (item.score >= 1500 && item.score <= 2999) {
                scoreCategory = "1500-2999";
            } else if (item.score >= 3000 && item.score <= 4499) {
                scoreCategory = "3000-4499";
            } else if (item.score >= 4500 && item.score <= 4999) {
                scoreCategory = "4500-4999";
            } else {
                scoreCategory = "5000";
            }
                return {
                    "lat": item.latLng.lat,
                    "lng": item.latLng.lng,
                    "heading": 0,
                    "pitch": 0,
                    "zoom": 0,
                    "panoId": null,
                    "countryCode": null,
                    "stateCode": null,
                    "extra": {
                        "tags": [item.gameMode,item.playerNick,item.forbidOptions,item.usingMap,item.country,scoreCategory]
                    }
                };
            })
        };


        var originalJson = JSON.stringify(coords, null, 2);
        var originalBlob = new Blob([originalJson], { type: 'application/json' });
        var originalUrl = URL.createObjectURL(originalBlob);

        var originalLink = document.createElement('a');
        originalLink.href = originalUrl;
        originalLink.download = 'original_data.json';
        document.body.appendChild(originalLink);
        originalLink.click();
        document.body.removeChild(originalLink);


        var convertedJson = JSON.stringify(convertedData, null, 2);
        var convertedBlob = new Blob([convertedJson], { type: 'application/json' });
        var convertedUrl = URL.createObjectURL(convertedBlob);

        var convertedLink = document.createElement('a');
        convertedLink.href = convertedUrl;
        convertedLink.download = 'map-making_data.json';
        document.body.appendChild(convertedLink);
        convertedLink.click();
        document.body.removeChild(convertedLink);
    }

    function clearLocalStorage() {
        localStorage.removeItem('activities');
        localStorage.removeItem('isUpdated');
    }

    function loadScript(url) {
        return new Promise((resolve, reject) => {
            var script = document.createElement('script');
            script.type = 'text/javascript';
            script.src = url;
            script.async = true;
            script.onload = resolve;
            script.onerror = reject;
            document.body.appendChild(script);
        });
    }

    function loadGoogleMapsAPI(apiKey, libraries) {
        var librariesParam = libraries ? '&libraries=' + libraries.join(',') : '';
        var url = 'https://maps.googleapis.com/maps/api/js?key=' + apiKey + librariesParam;

        return new Promise((resolve, reject) => {
            if (typeof google === 'undefined' || typeof google.maps === 'undefined') {
                loadScript(url)
                .then(resolve)
                .catch(() => {
                    reject(new Error('Failed to load Google Maps JavaScript API'));
                });
            } else {
                resolve();
            }
        });
    }

    function createHeatmap(coordinates) {
        let heatmapData=[]
        let filteredData
        let marker
        let markers = [];
        function filterHeatmapData(minScore, maxScore) {
            filteredData = heatmapData.filter(point => point.weight >= minScore && point.weight <= maxScore);
            heatmapLayer.setData(filteredData);
        }

        function createMap() {
                    var css = `
        #map-container {
            position: fixed;
            bottom: 0px;
            left: 40%;
            transform: translateX(-50%);
            width: 800px;
            height: 600px;
            z-index: 1;
        }

.control-panel {
    width: 120px;
    height: 30px;
    background-color: #fff;
    cursor: pointer;
    text-align: center;
    line-height: 30px;
}

.control-panel .select-container {
    display: none;
}

.control-panel:hover .select-container {
    display: block;
}

    .control-panel select {
        width: 100%;
        padding: 5px;
        border: 1px solid #ccc;
        border-radius: 5px;
        background-color: #fff;
        cursor: pointer;
        outline: none;
    }
    `;

            var style = document.createElement('style');
            style.textContent = css;
            document.head.appendChild(style);

            const mapContainer = document.createElement('div');
            mapContainer.id = 'map-container';
            document.body.appendChild(mapContainer);
            map = new google.maps.Map(mapContainer, {
                zoom: 2,
                center: { lat: 35.77, lng: 139.76 },
                mapTypeId: 'roadmap',
                gestureHandling: 'greedy',
                disableDefaultUI: true,
                streetViewControl: true
            });
            const controlDiv = document.createElement('div');
            controlDiv.className = 'control-panel';
            map.controls[google.maps.ControlPosition.TOP_LEFT].push(controlDiv);

            controlDiv.classList.add('hoverable');
            controlDiv.addEventListener('mouseenter', () => {
                selectContainer.style.display = 'block';
                controlDiv.style.height = '380px';
            });


            controlDiv.addEventListener('mouseleave', () => {
                selectContainer.style.display = 'none';
                controlDiv.style.height = '30px';
            });
            const layerCheckbox = document.createElement('input');
            layerCheckbox.type = 'checkbox';
            layerCheckbox.id = 'scatter'
            const label = document.createElement('label');
            label.htmlFor = 'scatter';
            label.textContent = 'Draw Scatter';
            label.style.color='#000'
            label.style.fontSize='16px'
            controlDiv.appendChild(layerCheckbox)
            controlDiv.appendChild(label);


const keysToFilter = ['playerNick', 'gameMode', 'forbidOptions', 'usingMap', 'country', 'score', 'distance', 'guessSeconds', 'gameDate'];
const optionValues = {};
let filters = [];
let players=[];

const selectContainer = document.createElement('div');
selectContainer.className = 'select-container';
controlDiv.appendChild(selectContainer);

keysToFilter.forEach(key => {

    const select = document.createElement('select');
    select.setAttribute('name', key);
    select.style.marginBottom = '15px';

    const defaultOption = document.createElement('option');
    defaultOption.setAttribute('value', '');
    defaultOption.textContent = `${key}`;
    select.appendChild(defaultOption);

    if (key === 'score' || key === 'distance' || key === 'guessSeconds') {
        const ranges = key === 'score' ? [
            { min: 0, max: 1499 },
            { min: 1500, max: 2999 },
            { min: 3000, max: 3999 },
            { min: 4000, max: 4499 },
            { min: 4500, max: 4999 },
            { min: 5000 }
        ] : key === 'distance' ? [
            { max: 0.5 },
            { min: 0.5, max: 5 },
            { min: 5, max: 100 },
            { min: 100, max: 1000 },
            { min: 1000, max: 5000 },
            { min: 5000 }
        ] : [
            { max: 5 },
            { min: 5, max: 15 },
            { min: 15, max: 30 },
            { min: 30, max: 60 },
            { min: 60, max: 300 },
            { min: 300 }
        ];

        for (let range of ranges) {
            const option = document.createElement('option');
            let label = range.max === undefined ? (key === 'score' ? '5000' : key === 'distance' ? '>5000km' : '>300s') : (key === 'score' ? `${range.min}-${range.max}` : key === 'distance' ? `${range.min}-${range.max}km` : `${range.min}-${range.max}s`);
            if (range.min==undefined&&key==='distance'){
                label='<0.5km';
            }
            if (range.min==undefined&&key==='guessSeconds'){
                label='<5s';
            }
            option.setAttribute('value', label);
            option.textContent = label;
            select.appendChild(option);
        }
    }
    else if (key === 'gameDate') {
        const dateRanges = [
            { label: 'More than 1 month', min:30, max: 1000 },
            { label: 'More than 15 days', min: 15, max: 30 },
            { label: 'More than 1 week', min: 7, max: 15 },
            { label: 'More than 1 day', min: 1, max: 7 },
            { label: 'Within 24 hours', min: 0, max: 1 }
        ];

        dateRanges.forEach(dateRange => {
            const option = document.createElement('option');
            option.setAttribute('value', `${dateRange.min}-${dateRange.max}` );
            option.textContent = dateRange.label;
            select.appendChild(option);

        });
    }
    else {
        const optionCounts = {};
        coordinates.forEach(item => {
            const value = item[key];
            optionCounts[value] = (optionCounts[value] || 0) + 1;
        });
        const sortedOptions = Object.keys(optionCounts).sort((a, b) => optionCounts[b] - optionCounts[a]);
        sortedOptions.forEach(value => {
            if (!optionValues[value]) {
                optionValues[value] = true;
                const option = document.createElement('option');
                option.setAttribute('value', value);
                if (key === 'playerNick') {
                    const myKey = Object.keys(myNick);
                    option.textContent = value;
                    if (value == myNick[myKey]) {
                        option.textContent = `${value}(me)`;
                        option.style.color = 'red';
                    }
                } else {
                    option.textContent = value;
                }
                select.appendChild(option);
            }
        });
    }

    selectContainer.appendChild(select);


select.addEventListener('change', () => {
    const selectedValue = select.value;
    filters[key] = selectedValue;

    let filteredData = coordinates

    Object.keys(filters).forEach(filterKey => {
        const filterValue = filters[filterKey];
        if (filterValue) {
            if (filterValue.includes('-')) {

                filteredData = filteredData.filter(item => {
                    if (filterKey=='gameDate'){
                        const [minDays, maxDays] = filterValue.split('-')
                        const itemValue = parseFloat(item[filterKey])
                        return itemValue > minDays && itemValue <= maxDays;
                    }
                    else{
                        const [minValue, maxValue] = filterValue.split('-').map(val => parseFloat(val));
                        const itemValue = parseFloat(item[filterKey]);
                        return itemValue >= minValue && itemValue <= maxValue;}
                });
            } else if (filterValue.includes('>') || filterValue.includes('<')) {
                const operator = filterValue.includes('>') ? '>' : '<';
                const value = parseFloat(filterValue.substring(1));
                filteredData = filteredData.filter(item => {
                    const itemValue = parseFloat(item[filterKey]);
                    return operator === '>' ? itemValue > value : itemValue < value;
                });
            } else if (filterValue.includes('5000')) {
                filteredData = filteredData.filter(item => {
                    const itemValue = parseFloat(item[filterKey]);
                    return itemValue === 5000;
                });}
             else {
                filteredData = filteredData.filter(item => item[filterKey] === filterValue);
            }
        }
    });

    refreshHeatmap(filteredData, 'score');
});
            });

            layerCheckbox.addEventListener('change', (event) => {
                if (heatmapData.length==0){
                    for (const coord of coordinates) {
                        heatmapData.push({
                            location: new google.maps.LatLng(coord.latLng.lat, coord.latLng.lng),
                            weight: coord.score
                        });
        }
                }
                if (event.target.checked) {
                    for (const coord of heatmapData) {
                        const marker = new google.maps.Marker({
                            position: new google.maps.LatLng(coord.location),
                            map: map,
                            title: 'Score: ' + coord.weight,
                            pin: {
                                fillOpacity: 1,
                                strokeWeight: 0,
                                color:'gray',
                                scale: (coord.weight/5000)
                            }
                        });
                        markers.push(marker);
                        marker.addListener('click', () => {
                            createStreetViewContainer(coord.location.lat(),coord.location.lng())

                        });
                    }



                } else {
                        markers.forEach(marker => marker.setMap(null));
                        markers = [];

                       }
            })

        }

        function refreshHeatmap(fd,k) {
            if (fd.length === 0) {
                console.error('No valid coordinates data found');
            }
            heatmapData=[]
            for (const coord of fd) {
                heatmapData.push({
                    location: new google.maps.LatLng(coord.latLng.lat, coord.latLng.lng),
                    weight: coord[k]
                });
            }
            heatmapLayer.setData(heatmapData);
            if (markers.length!=0){
                markers.forEach(marker => marker.setMap(null));
                markers = [];
            }
        }

        if (!map) {
            createMap();
            getDefault(coordinates)
        }

    }

    function getDefault(coordinates){
        var h=[];
        if (heatmapLayer) {
            heatmapLayer.setMap(null);
        }
        for (const coord of coordinates) {
            h.push({
                location: new google.maps.LatLng(coord.latLng.lat, coord.latLng.lng),
                weight: coord.score
            });
        }

        heatmapLayer = new google.maps.visualization.HeatmapLayer({
            data: h,
            dissipating: true,
            map: map,
        });}

    function getStatics(url, result, maxPages,currentPage = 1, pageToken = null) {
        if (currentPage==1){
            const swal = Swal.fire({
                title: 'Fetching Activities',
                text: 'Please wait...',
                allowOutsideClick: false,
                allowEscapeKey: false,
                showConfirmButton: false,
                didOpen: () => {
                    Swal.showLoading();
                }
            });
        }
        if (currentPage > maxPages) {
            console.log(`Reached maximum number of pages (${maxPages}). Stopping requests.`);
            swal.close()
            Swal.fire('Success', 'All activities retrieved successfully!', 'success');
            saveLocalStorage(result)
            getCoords(result);
            return;
        }

        let nextPageUrl = url;
        if (pageToken) {
            nextPageUrl += `&paginationToken=${encodeURIComponent(pageToken)}`;
        }

        GM_xmlhttpRequest({
            method: "GET",
            url: nextPageUrl,
            onload: function(response) {
                if (response.status === 200) {
                    const data = JSON.parse(response.responseText);
                    processActivities(data, result);
                    const nextPageToken = data.paginationToken;
                    if (nextPageToken) {
                        getStatics(url, result, maxPages, currentPage + 1, nextPageToken);
                    } else {
                        Swal.fire('Success', 'All activities retrieved successfully!', 'success');
                        saveLocalStorage(result)
                        getCoords(result);
                    }
                } else {
                    console.error('Request failed: ' + response.statusText);
                    Swal.fire('Error', 'Failed to fetch activities. Please try again later.', 'error');
                }
            },
            onerror: function(response) {
                console.error('Request failed: ' + response.statusText);
                Swal.fire('Error', 'Failed to fetch activities. Please try again later.', 'error');
            }
        });
    }

    function processActivities(data, result) {
        const entries = data.entries;
        if (entries && entries.length > 0) {
            entries.forEach(entry => {
                if (entry.payload) {
                    const payloadList = JSON.parse(entry.payload);
                    const userId = entry.user.id;
                    if (!Array.isArray(payloadList)) {
                        processPayload(payloadList, userId, result);
                    } else {
                        payloadList.forEach(payload => {
                            processPayload(payload, userId, result);
                        });
                    }
                }
            });
        } else {
            console.error('Data not found!');
        }
    }

    function processPayload(payload, userId, result) {
        if (payload.gameToken) {
            result.games[userId] = result.games[userId] || [];
            if (!result.games[userId].includes(payload.gameToken)) {
                result.games[userId].push(payload.gameToken);
            }
        } else if (payload.gameId) {
            result.duels[userId] = result.duels[userId] || [];
            if (!result.duels[userId].includes(payload.gameId)) {
                result.duels[userId].push(payload.gameId);
            }
        }
    }

    async function getCoords(data) {
        try {
            var coordinates = [];
            const duelsPromises = [];
            const gamesPromises = [];
            const chunkSize = 20;
            const swal = Swal.fire({
                title: 'Fetching Coordinates',
                text: 'Please wait...',
                allowOutsideClick: false,
                allowEscapeKey: false,
                showConfirmButton: false,
                didOpen: () => {
                    Swal.showLoading();
                }
            });
            for (const gameIds of Object.values(data.duels)) {
                for (let i = 0; i < gameIds.length; i += chunkSize) {
                    const chunk = gameIds.slice(i, i + chunkSize);
                    const chunkPromises = chunk.map(gameId => {
                        const requestUrl = `https://game-server.geoguessr.com/api/duels/${gameId}`;
                        return getGameSummary(requestUrl, 'duels', coordinates);
                    });
                    duelsPromises.push(Promise.all(chunkPromises));
                }
            }

            for (const gameIds of Object.values(data.games)) {
                for (let i = 0; i < gameIds.length; i += chunkSize) {
                    const chunk = gameIds.slice(i, i + chunkSize);
                    const chunkPromises = chunk.map(gameId => {
                        const requestUrl = `https://www.geoguessr.com/api/v3/games/${gameId}?client=web`;
                        return getGameSummary(requestUrl, 'games', coordinates);
                    });
                    gamesPromises.push(Promise.all(chunkPromises));
                }
            }

            await Promise.all([...duelsPromises, ...gamesPromises]);
            swal.close();
            try {
                const friends = await getFriends();
                const matchedData = matchPlayerNick(coordinates, friends);
                createHeatmap(matchedData);
                coordinates=matchedData
                Swal.fire('Success', 'Heatmap is prepared!', 'success');
            } catch (error) {
                console.error("Error:", error);
                Swal.fire('Error', 'Failed to prepare heatmap', 'error');
            }
            await downloadJSON(coordinates);
        } catch (error) {
            console.error('Error parsing JSON from localStorage:', error);
        }
    }

    async function getGameSummary(url, mode, coordinates) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: url,
                onload: function(response) {
                    try {let forbidOptions
                        const data = JSON.parse(response.responseText);
                        if(data.teams||data.player){
                            if (mode === 'duels') {
                                const movementOptions=data.movementOptions
                                if(movementOptions.forbidMoving&&movementOptions.forbidZooming&&movementOptions.forbidRotating){forbidOptions='NMPZ'}
                                else if (!movementOptions.forbidMoving&&!movementOptions.forbidZooming&&!movementOptions.forbidRotating){forbidOptions='Moving'}
                                else if(movementOptions.forbidMoving&&!movementOptions.forbidZooming&&!movementOptions.forbidRotating){forbidOptions='NoMoving'}
                                else{forbidOptions='Entertainment'}
                            const usingMap=data.options.map.name
                            data.teams.forEach(team => {
                                team.players.forEach(player => {
                                    player.guesses.forEach(guess => {
                                        const roundNumber = guess.roundNumber;
                                        const roundData = data.rounds.find(round => round.roundNumber === roundNumber);
                                        if (roundData) {
                                            const gameDate = guess.created.substring(0, 10);
                                            const gameMode = 'duels';
                                            const latLng = { 'lat': roundData.panorama.lat, 'lng': roundData.panorama.lng };
                                            const country = roundData.panorama.countryCode;
                                            const playerId = player.playerId;
                                            const score = team.roundResults.find(result => result.roundNumber === roundNumber).score;
                                            const distance = (guess.distance / 1000).toFixed(2);
                                            const guessSeconds = (Math.abs(new Date(guess.created) - new Date(roundData.startTime)) / 1000).toFixed(2);
                                            coordinates.push({ gameMode, playerId, latLng, country, score, distance, guessSeconds, forbidOptions, usingMap, gameDate });
                                        }
                                    });
                                });
                            });
                        }
                        else {
                            if(data.forbidMoving&&data.forbidZooming&&data.forbidRotating){forbidOptions='NMPZ'}
                            else if (!data.forbidMoving&&!data.forbidZooming&&!data.forbidRotating){forbidOptions='Moving'}
                            else if(data.forbidMoving&&!data.forbidZooming&&!data.forbidRotating){forbidOptions='NoMoving'}
                            else{forbidOptions='Entertainment'}
                            const gameMode = 'classic';
                            const player = data.player;
                            const playerId = player.id;
                            const usingMap=data.mapName
                            player.guesses.forEach((guess, index) => {
                                const roundData = data.rounds[index];
                                const gameDate = roundData.startTime.substring(0, 10);
                                const latLng = { 'lat': roundData.lat, 'lng': roundData.lng };
                                const country = roundData.streakLocationCode;
                                const score = guess.roundScoreInPoints;
                                const distance = parseFloat(guess.distance.meters.amount);
                                const guessSeconds = guess.time;
                                coordinates.push({ gameMode, playerId, latLng, country, score, distance, guessSeconds, forbidOptions, usingMap, gameDate });
                            });
                        }}
                        resolve();
                    } catch (error) {
                        console.error(`Error parsing JSON from URL: ${url}`, error);
                    }
                },
                onerror: function(error) {
                    reject(error);
                }
            });
        });
    }

    async function getPlayerName(id) {
        return new Promise((resolve, reject) => {
            const url = `https://www.geoguessr.com/user/${id}`;
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                onload: function(response) {
                    if (response.status === 200) {
                        const playerName = extractPlayerName(response.responseText);
                        resolve(playerName);
                    } else {
                        reject('Error:', response.status);
                    }
                },
                onerror: function(error) {
                    reject('Error:', error);
                }
            });
        });
    }

    function extractPlayerName(responseText) {
        const regex = /"user"\s*:\s*{\s*"nick"\s*:\s*"(.+?)"/;
        const match = responseText.match(regex);
        if (match && match.length > 1) {
            return match[1];
        }
        return null;
    }

    function createStreetViewContainer(latitude, longitude,mapContainer) {
        if (streetViewContainer) {
            streetViewContainer.remove();
        }
        if (streetViewMap){streetViewMap.setStreetView(null)
                          }
        streetViewContainer = document.createElement('div');
        streetViewContainer.id = 'street-view-container';
        streetViewContainer.style.position = 'fixed';
        streetViewContainer.style.bottom = '0px';
        streetViewContainer.style.left = '0px';
        streetViewContainer.style.width = '800px';
        streetViewContainer.style.height = '600px';
        streetViewContainer.style.overflow = 'hidden';
        streetViewContainer.style.zIndex = '1';

        streetViewMap = new google.maps.Map(streetViewContainer, {
            center: { lat: latitude, lng: longitude },
            zoom: 14,
            streetViewControl: true
        });

        var closeButton = document.createElement('div');
        closeButton.className = 'custom-close-button';
        closeButton.innerHTML = '×';
        closeButton.style.position = 'absolute';
        closeButton.style.top = '1px';
        closeButton.style.right = '80px';
        closeButton.style.margin = '10px';
        closeButton.style.padding = '0px';
        closeButton.style.background = 'none';
        closeButton.style.border = '0px';
        closeButton.style.textTransform = 'none';
        closeButton.style.appearance = 'none';
        closeButton.style.cursor = 'pointer';
        closeButton.style.userSelect = 'none';
        closeButton.style.borderRadius = '2px';
        closeButton.style.height = '40px';
        closeButton.style.width = '40px';
        closeButton.style.boxShadow = 'rgba(0, 0, 0, 0.3) 0px 1px 4px -1px';
        closeButton.style.overflow = 'hidden';
        closeButton.style.color = '#fff';
        closeButton.style.fontSize = '24px';
        closeButton.style.lineHeight = '40px';
        closeButton.style.textAlign = 'center';
        closeButton.style.backgroundColor = 'rgb(34, 34, 34)';
        closeButton.onclick = function() {
            streetViewContainer.remove();
        };
         closeButton.style.zIndex = '2';
        closeButton.onclick = function() {
            streetViewContainer.remove();
        };
        streetViewContainer.appendChild(closeButton);
        var streetViewService = new google.maps.StreetViewService();
        streetViewService.getPanorama({ location: { lat: latitude, lng: longitude }, radius: 50 }, function(data, status) {
            if (status === 'OK') {
                var streetView = new google.maps.StreetViewPanorama(streetViewContainer, {
                    position: data.location.latLng,
                    pov: { heading: 235, pitch: 10 }
                });
                streetViewMap.setStreetView(streetView);
            } else {
                console.error('Street View data not found for this location');
            }
        });

        document.body.appendChild(streetViewContainer);
    }

    async function swalOption(){
        const { value: inputOption,dismiss: inputDismiss } =await Swal.fire({
            title: 'Get Activities',
            text: 'Do you want to fetch activities from your Geoguessr account? If you click "Cancel", you will need to upload a JSON file',
            icon: 'question',
            showCancelButton: true,
            showCloseButton:true,
            allowOutsideClick: false,
            input: 'number',
            inputLabel: 'Set A Limit Of Activities Pages',
            inputPlaceholder: '10',
            inputAttributes: {
                min:1,
                max: 100,
                step:10,
            },
            confirmButtonColor: '#3085d6',
            cancelButtonColor: '#d33',
            confirmButtonText: 'Yes',
            cancelButtonText: 'Cancel',
            inputValidator: (value) => {
                if (!value||parseInt(value)<1) {
                    return 'Please set a valid limit number';
                }
                if (parseInt(value) > 100) {
                    return 'It is recommended that the maximum number of pages should not exceed 100!';
                }
            }
        });

        if (inputOption) {
            if (!isUpdated){
                const pageValue=parseInt(inputOption)
                getStatics(u,activities,pageValue)}
            else{
                getCoords(activities)}
        }
        else if(inputDismiss==='cancel'){
            const input = document.createElement('input');
            input.type = 'file';
            input.style.display = 'none'
            document.body.appendChild(input);

            const coordsPromise = 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) {
                            Swal.fire('Error Parsing JSON Data!', 'The input JSON data is invalid or incorrectly formatted.','error');
                        }
                    };

                    reader.readAsText(file);
                });


                input.click();
            });
            coordsPromise.then(async (data) => {
                try {
                    const friends = await getFriends();
                    var matchedData = matchPlayerNick(data, friends);
                    matchedData=getDiffdays(matchedData)
                    createHeatmap(matchedData);
                    Swal.fire('Success', 'Heatmap is prepared!', 'success');
                } catch (error) {
                    console.error("Error:", error);
                    Swal.fire('Error', 'Failed to prepare heatmap', 'error');
                }
});
        }
    }

    function createButton() {

        var mapButton = document.createElement('button');
        mapButton.textContent = 'Create Heatmap';
        mapButton.addEventListener('click',swalOption);
        mapButton.style.position = 'fixed';
        mapButton.style.top = '10px';
        mapButton.style.right = '360px';
        mapButton.style.zIndex = '9999';
        document.body.appendChild(mapButton);

        var refreshButton = document.createElement('button');
        refreshButton.textContent = 'Update Your Activities';
        refreshButton.addEventListener('click', function(){getStatics(u,activities,10)});
        refreshButton.style.position = 'fixed';
        refreshButton.style.top = '10px';
        refreshButton.style.right = '200px';
        refreshButton.style.zIndex = '9999';
        document.body.appendChild(refreshButton);

        var clearButton = document.createElement('button');
        clearButton.textContent = 'Clear Activities';
        clearButton.addEventListener('click', clearLocalStorage);
        clearButton.style.position = 'fixed';
        clearButton.style.top = '10px';
        clearButton.style.right = '80px';
        clearButton.style.zIndex = '9999';
        document.body.appendChild(clearButton);
    }

    loadGoogleMapsAPI('AIzaSyAiRLvmrxcqZRhsiPMzK5Ps2b5Ov6XhJrY', ['visualization','streetView'])
    .then(createButton)
    .catch(error => {
        console.error(error);
    });
})();