Pano Detective

Find the exact time a Google Street View image was taken (default coverage)

// ==UserScript==
// @name         Pano Detective
// @namespace    https://greasyfork.org/users/1179204
// @version      1.8.5
// @description  Find the exact time a Google Street View image was taken (default coverage)
// @author       KaKa
// @match        *://maps.google.com/*
// @match        *://www.google.com/*
// @match        *://*.google.com/maps/*
// @match        *://*.google.ru/maps/*
// @match        *://*.google.de/maps/*
// @match        *://*.google.fr/maps/*
// @match        *://*.google.ca/maps/*
// @match        *://*.google.it/maps/*
// @match        *://*.google.pl/maps/*
// @match        *://*.google.se/maps/*
// @match        *://*.google.co.uk/maps/*
// @match        *://*.google.co.jp/maps/*
// @match        *://*.google.co.id/maps/*
// @match        *://*.google.co.in/maps/*
// @match        *://*.google.co.kr/maps/*
// @match        *://*.google.co.za/maps/*
// @match        *://*.google.co.th/maps/*
// @match        *://*.google.com.hk/maps/*
// @match        *://*.google.com.br/maps/*
// @match        *://*.google.com.mx/maps/*
// @match        *://*.google.com.ph/maps/*
// @match        *://*.google.com.ar/maps/*
// @match        *://*.google.com.co/maps/*
// @match        *://*.google.com.tw/maps/*
// @exclude      https://ogs.google.com
// @exclude      https://accounts.google.com
// @exclude      https://clients5.google.com
// @icon         https://www.svgrepo.com/show/485785/magnifier.svg
// @require      https://cdn.jsdelivr.net/npm/sweetalert2@11
// @require      https://cdn.jsdelivr.net/npm/[email protected]/lib/chinese-lunar.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/geotz.min.js
// @grant        GM_xmlhttpRequest
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @license      BSD
// ==/UserScript==


(function() {
    const DATE_FORMAT=0    // 0:default(locale format) 1:yyyy-mm-dd  2:yyyy/mm/dd  3:dd/mm/yyyy  4:mm/dd/yyyy  5:Month dd, yyyy  6:dd Month, yyyy 7:Lunar

    const TIME_FORMAT=1     // 1:(hh:mm:ss)  2:(hh:mm:ss am/pm)

    const ACCURACY=2;       // default setting is 2 seconds

    const DISTANCE_UNITS='METERS' // or MILES

    /* ----- API KEY INSTRUCTIONS -----
    This feature is originally made by miraclewhips. 

    Requires an API key from Map Making App in order to save locations.
    Create one here: https://map-making.app/keys
    Make sure not to share this key with anybody or show it publicly as it will allow anybody to edit your maps.

    Replace `PASTE_YOUR_KEY_HERE` with your generated API key (make sure not to delete the quotes surrounding the key) */
    const MAP_MAKING_API_KEY = "PASTE_YOUR_KEY_HERE";

    // Number of maps to show in the "Recent Maps" section
    const NUMBER_OF_RECENT_MAPS = 3;


    /*===================================================================================================================================================================================================================================
  ===================================================================================================================================================================================================================================
  ===================================================================================================================================================================================================================================
  ===================================================================================================================================================================================================================================*/

    GM_addStyle(`
.mwstmm-modal {
	position: fixed;
	inset: 0;
	z-index: 99999;
	display: flex;
	align-items: center;
	justify-content: center;
	flex-direction: column;
}

.mwstmm-modal .dim {
	position: fixed;
	inset: 0;
	z-index: 0;
	background: rgba(0,0,0,0.75);
}

.mwstmm-modal .text {
	position: relative;
	z-index: 1;
}

.mwstmm-modal .inner {
	box-sizing: border-box;
	position: relative;
	z-index: 1;
	background: #fff;
	padding: 20px;
	margin: 20px;
	width: calc(100% - 40px);
	max-width: 500px;
	overflow: auto;
	color: #000;
	flex: 0 1 auto;
}

#mwstmm-loader {
	color: #fff;
	font-weight: bold;
}
.mwstmm-settings {
	position: absolute;
	top: 1rem;
	left: 1rem;
	z-index: 9;
	display: flex;
	flex-direction: column;
	gap: 5px;
	align-items: flex-start;
}
#mwstmm-main {
 	position: absolute;
    width:36px;
    height:36px;
	top: 0.9rem;
	right: 4rem;
 	z-index: 9;
 	display: flex;
    border: none;
    border-radius: 50%;
    background: #00000099;
    background-repeat: no-repeat;
    background-position:50%;
 	flex-direction: column;
 	gap: 5px;
 	align-items: flex-start;
 }

#mwstmm-main:hover{
    cursor: pointer;
}

#mwstmm-main::after{
    content: attr(data-text);
    position:absolute;
    top:120%;
    transform: translateX(-50%);
    background-color: rgba(0, 0, 0, 0.8);
    color: #fff;
    padding: 5px;
    border-radius: 5px;
    font-weight:normal;
    font-size: 13px;
    line-height: 1;
    height: auto;
    white-space: nowrap;
    opacity: 0;
    transition: opacity 0.5s ease;
}

#mwstmm-main:hover::after {
    opacity: 1;
}

.mwstmm-settings.extra-pad {
	top: 2.5rem;
}

.mwstmm-title {
	font-size: 15px;
	font-weight: bold;
	text-shadow: rgb(204, 48, 46) 2px 0px 0px, rgb(204, 48, 46) 1.75517px 0.958851px 0px, rgb(204, 48, 46) 1.0806px 1.68294px 0px, rgb(204, 48, 46) 0.141474px 1.99499px 0px, rgb(204, 48, 46) -0.832294px 1.81859px 0px, rgb(204, 48, 46) -1.60229px 1.19694px 0px, rgb(204, 48, 46) -1.97998px 0.28224px 0px, rgb(204, 48, 46) -1.87291px -0.701566px 0px, rgb(204, 48, 46) -1.30729px -1.5136px 0px, rgb(204, 48, 46) -0.421592px -1.95506px 0px, rgb(204, 48, 46) 0.567324px -1.91785px 0px, rgb(204, 48, 46) 1.41734px -1.41108px 0px, rgb(204, 48, 46) 1.92034px -0.558831px 0px;
	position: relative;
	z-index: 1;
}

.mwstmm-subtitle {
	font-size: 12px;
	background: rgba(204, 48, 46, 0.4);
	padding: 3px 5px;
	border-radius: 5px;
	position: relative;
	z-index: 0;
	top: -8px;
	text-shadow: 0 1px 2px rgba(0,0,0,0.5);
}

.mwstmm-subtitle a:hover {
	text-decoration: underline;
}

.mwstmm-settings-option {
	background: var(--ds-color-purple-100);
	padding: 6px 10px;
	border-radius: 5px;
	font-size: 12px;
	cursor: pointer;
	opacity: 0.75;
	transition: opacity 0.2s;
	pointer-events: auto;
}

.mwstmm-settings-option:hover {
	opacity: 1;
}

#mwstmm-map-list h3 {
	margin-bottom: 10px;
}

#mwstmm-map-list .tag-input {
	display: block;
	width: 100%;
	font: inherit;
    border:1px solid #ccc;
}

#mwstmm-map-list .maps {
	max-height: 200px;
	overflow-x: hidden;
	overflow-y: auto;
	font-size: 15px;
}

#mwstmm-map-list .map {
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: 20px;
	padding: 8px;
	transition: background 0.2s;
}

#mwstmm-map-list .map:nth-child(2n) {
	background: #f0f0f0;
}

#mwstmm-map-list .map-buttons:not(.is-added) .map-added {
	display: none !important;
}
#mwstmm-map-list .map-buttons.is-added .map-add {
	display: none !important;
}

#mwstmm-map-list .map-add {
	background: green;
	color: #fff;
	padding: 3px 6px;
	border-radius: 5px;
	font-size: 13px;
	font-weight: bold;
	cursor: pointer;
}

#mwstmm-map-list .map-added {
	background: #000;
	color: #fff;
	padding: 3px 6px;
	border-radius: 5px;
	font-size: 13px;
	font-weight: bold;
}

div[class^="result-list_listItemWrapper__"] {
	position: relative;
}

div[class^="result-list_listItemWrapper__"] .mwstmm-settings-option {
	margin-left: auto;
	line-height: 1;
	align-self: center;
}
`);

    let detectButton,downloadButton,previousListener,zoomLevel,w,h,formattedTime,capturePano,type
    let LOCATION, ROUNDS;
    let MAP_LIST;
    let previousMapId=JSON.parse(GM_getValue('previousMapId', null));


    let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun','Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
    let dateSvg=`<svg width="22px" height="22px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#9AA0a6"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g id="Calendar / Calendar_Days"> <path id="Vector" d="M8 4H7.2002C6.08009 4 5.51962 4 5.0918 4.21799C4.71547 4.40973 4.40973 4.71547 4.21799 5.0918C4 5.51962 4 6.08009 4 7.2002V8M8 4H16M8 4V2M16 4H16.8002C17.9203 4 18.4796 4 18.9074 4.21799C19.2837 4.40973 19.5905 4.71547 19.7822 5.0918C20 5.5192 20 6.07899 20 7.19691V8M16 4V2M4 8V16.8002C4 17.9203 4 18.4801 4.21799 18.9079C4.40973 19.2842 4.71547 19.5905 5.0918 19.7822C5.5192 20 6.07899 20 7.19691 20H16.8031C17.921 20 18.48 20 18.9074 19.7822C19.2837 19.5905 19.5905 19.2842 19.7822 18.9079C20 18.4805 20 17.9215 20 16.8036V8M4 8H20M16 16H16.002L16.002 16.002L16 16.002V16ZM12 16H12.002L12.002 16.002L12 16.002V16ZM8 16H8.002L8.00195 16.002L8 16.002V16ZM16.002 12V12.002L16 12.002V12H16.002ZM12 12H12.002L12.002 12.002L12 12.002V12ZM8 12H8.002L8.00195 12.002L8 12.002V12Z" stroke="#9AA0a6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g> </g></svg>`
    let date_Svg=`<svg width="22px" height="22px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g id="Calendar / Calendar_Days"> <path id="Vector" d="M8 4H7.2002C6.08009 4 5.51962 4 5.0918 4.21799C4.71547 4.40973 4.40973 4.71547 4.21799 5.0918C4 5.51962 4 6.08009 4 7.2002V8M8 4H16M8 4V2M16 4H16.8002C17.9203 4 18.4796 4 18.9074 4.21799C19.2837 4.40973 19.5905 4.71547 19.7822 5.0918C20 5.5192 20 6.07899 20 7.19691V8M16 4V2M4 8V16.8002C4 17.9203 4 18.4801 4.21799 18.9079C4.40973 19.2842 4.71547 19.5905 5.0918 19.7822C5.5192 20 6.07899 20 7.19691 20H16.8031C17.921 20 18.48 20 18.9074 19.7822C19.2837 19.5905 19.5905 19.2842 19.7822 18.9079C20 18.4805 20 17.9215 20 16.8036V8M4 8H20M16 16H16.002L16.002 16.002L16 16.002V16ZM12 16H12.002L12.002 16.002L12 16.002V16ZM8 16H8.002L8.00195 16.002L8 16.002V16ZM16.002 12V12.002L16 12.002V12H16.002ZM12 12H12.002L12.002 12.002L12 12.002V12ZM8 12H8.002L8.00195 12.002L8 12.002V12Z" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g> </g></svg>`
    let iconSvg=`<svg width="22px" height="22px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g id="Interface / Download"> <path id="Vector" d="M6 21H18M12 3V17M12 17L17 12M12 17L7 12" stroke="#9AA0a6" stroke-width="2.16" stroke-linecap="round" stroke-linejoin="round"></path> </g> </g></svg>`
    let icon_Svg=`<svg width="22px" height="22px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g id="Interface / Download"> <path id="Vector" d="M6 21H18M12 3V17M12 17L17 12M12 17L7 12" stroke="#ffffff" stroke-width="2.16" stroke-linecap="round" stroke-linejoin="round"></path> </g> </g></svg>`
    let saveSvg=`<svg width="16px" height="16px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M17 9.00195C19.175 9.01406 20.3529 9.11051 21.1213 9.8789C22 10.7576 22 12.1718 22 15.0002V16.0002C22 18.8286 22 20.2429 21.1213 21.1215C20.2426 22.0002 18.8284 22.0002 16 22.0002H8C5.17157 22.0002 3.75736 22.0002 2.87868 21.1215C2 20.2429 2 18.8286 2 16.0002L2 15.0002C2 12.1718 2 10.7576 2.87868 9.87889C3.64706 9.11051 4.82497 9.01406 7 9.00195" stroke="#FFFFFF" stroke-width="1.5" stroke-linecap="round"></path> <path d="M12 15L12 2M12 2L15 5.5M12 2L9 5.5" stroke="#FFFFFF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>`

    const iconUrl=svgToUrl(iconSvg)
    const icon_Url=svgToUrl(icon_Svg)
    const svgUrl=svgToUrl(dateSvg)
    const svg_Url=svgToUrl(date_Svg)
    const saveUrl=svgToUrl(saveSvg)
    const moon_phase=['🌑','🌒','🌓','🌔','🌕','🌖','🌗','🌘']
    const mountain = "⛰️";
    const wave = "🌊";

    function svgToUrl(svgText) {
        const svgBlob = new Blob([svgText], {type: 'image/svg+xml'});
        const svgUrl = URL.createObjectURL(svgBlob);
        return svgUrl;
    }

    function defaultState() {
        return {
            recentMaps: []
        }
    }

    function loadState() {
        const data = GM_getValue('mwstmm_state', null)
        if(!data) return;

        const dataJson = JSON.parse(data);
        if(!data) return;

        Object.assign(MWSTMM_STATE, defaultState(), dataJson);
        saveState();
    }

    function saveState() {
        GM_setValue('mwstmm_state', JSON.stringify(MWSTMM_STATE));
    }

    const MWSTMM_STATE = defaultState();
    loadState();

    async function mmaFetch(url, options = {}) {
        const response = await fetch(new URL(url, 'https://map-making.app'), {
            ...options,
            headers: {
                accept: 'application/json',
                authorization: `API ${MAP_MAKING_API_KEY.trim()}`,
                ...options.headers
            }
        });
        if (!response.ok) {
            let message = 'Unknown error';
            try {
                const res = await response.json();
                if (res.message) {
                    message = res.message;
                }
            } catch {
                //empty
            }
            alert(`An error occurred while trying to connect to Map Making App. ${message}`);
            throw Object.assign(new Error(message), { response });
        }
        return response;
    }
    async function getMaps() {
        const response = await mmaFetch(`/api/maps`);
        const maps = await response.json();
        return maps;
    }
    async function importLocations(mapId, locations) {
        const response = await mmaFetch(`/api/maps/${mapId}/locations`, {
            method: 'post',
            headers: {
                'content-type': 'application/json'
            },
            body: JSON.stringify({
                edits: [{
                    action: { type: 4 },
                    create: locations,
                    remove: []
                }]
            })
        });
        await response.json();
    }

    function extractParams(link) {
        const regex = /@(-?\d+\.\d+),(-?\d+\.\d+),.*?\/data=!3m\d+!1e\d+!3m\d+!1s([^!]+)!/;

        const match = link.match(regex);

        if (match && match.length === 4) {
            var lat = match[1];
            var lng = match[2];
            var panoId = match[3];
            return {lat,lng,panoId}
        } else {
            console.error('Invalid Google Street View link format');
            return null;
        }
    }

    function showLoader() {
        if(document.getElementById('mwstmm-loader')) return;

        const element = document.createElement('div');
        element.id = 'mwstmm-loader';
        element.className = 'mwstmm-modal';
        element.innerHTML = `
		<div class="text">LOADING...</div>
		<div class="dim"></div>
	`;
        document.body.appendChild(element);
    }

    function hideLoader() {
        const element = document.getElementById('mwstmm-loader');
        if(element) element.remove();
    }

    async function clickedMapButton() {
        if(MAP_MAKING_API_KEY === 'PASTE_YOUR_KEY_HERE') {
            alert('An API Key is required in order to save locations to Map Making App. Please add your API key by editing the Userscript and following the instructions at the top of the script.');
            return;
        }
        if(!MAP_LIST) {
            showLoader();

            try {
                MAP_LIST = await getMaps();
            }catch{
                //empty
            }

            hideLoader();
        }

        if(MAP_LIST) {
            showMapList()
        }
    }

    function showMapList() {
        if(document.getElementById('mwstmm-map-list')) return;

        const element = document.createElement('div');
        element.id = 'mwstmm-map-list';
        element.className = 'mwstmm-modal';

        let recentMapsSection = ``;
        if(NUMBER_OF_RECENT_MAPS > 0 && MWSTMM_STATE.recentMaps.length > 0) {
            let recentMapsHTML = '';
            for(const m of MWSTMM_STATE.recentMaps) {
                if(m.archivedAt) continue;
                recentMapsHTML += `<div class="map">
                <a href="https://map-making.app/maps/${m.id}" class="map-link">
				    <span class="map-name">${m.name}</span>
                </a>
				<span class="map-buttons">
					<span class="map-add" data-id="${m.id}">ADD</span>
					<span class="map-added">ADDED</span>
				</span>
			</div>`;
            }

            recentMapsSection = `
			<h3>Recent Maps</h3>

			<div class="maps">
				${recentMapsHTML}
			</div>

			<br>
		`;
        }

        let mapsHTML = '';
        for(const m of MAP_LIST) {
            if(m.archivedAt) continue;
            mapsHTML += `<div class="map">
            <a href="https://map-making.app/maps/${m.id}" class="map-link">
			    <span class="map-name">${m.name}</span>
            </a>
			<span class="map-buttons">
				<span class="map-add" data-id="${m.id}">ADD</span>
				<span class="map-added">ADDED</span>
			</span>
		</div>`;
        }

        element.innerHTML = `
		<div class="inner">
			<h3>Tags (comma separated)</h3>

			<input type="text" class="tag-input" id="mwstmm-map-tags" />

			<br><br>

			${recentMapsSection}

			<h3>All Maps</h3>

			<div class="maps">
				${mapsHTML}
			</div>
		</div>

		<div class="dim"></div>
	`;

        document.body.appendChild(element);

        element.querySelector('.dim').addEventListener('click', closeMapList);

        document.getElementById('mwstmm-map-tags').addEventListener('keyup', e => e.stopPropagation());
        document.getElementById('mwstmm-map-tags').addEventListener('keydown', e => e.stopPropagation());
        document.getElementById('mwstmm-map-tags').addEventListener('keypress', e => e.stopPropagation());
        document.getElementById('mwstmm-map-tags').focus();

        for(const map of element.querySelectorAll('.maps .map-add')) {
            map.addEventListener('click', addLocationToMap);
        }
    }

    function closeMapList() {
        const element = document.getElementById('mwstmm-map-list');
        if(element) element.remove();
    }

    function addLocationToMap(e) {
        e.target.parentNode.classList.add('is-added');

        const id = parseInt(e.target.dataset.id);
        previousMapId=id
        GM_setValue('previousMapId', JSON.stringify(previousMapId));

        if(NUMBER_OF_RECENT_MAPS > 0) {
            MWSTMM_STATE.recentMaps = MWSTMM_STATE.recentMaps.filter(e => e.id !== id).slice(0, NUMBER_OF_RECENT_MAPS-1);
            for(const map of MAP_LIST) {
                if(map.id === id) {
                    MWSTMM_STATE.recentMaps.unshift(map);
                    break;
                }
            }
        }
        saveState();

        importLocations(id, [{
            id: -1,
            location: {lat: LOCATION.lat, lng: LOCATION.lng},
            panoId: LOCATION.panoId ?? null,
            heading: LOCATION.heading ?? 90,
            pitch: LOCATION.pitch ?? 0,
            zoom: LOCATION.zoom === 0 ? null : LOCATION.zoom,
            tags: LOCATION.tags,
            flags: LOCATION.panoId ? 1 : 0
        }]);
    }

    function addSettingsButtonsToPage() {
        const container = document.getElementById('image-header');
        if(!container || document.getElementById('mwstmm-main')) return;
        const element = document.createElement('div');

        element.id = 'mwstmm-main';

        element.style.backgroundImage=`url(${saveUrl})`
        element.style.fontSize='8px'
        element.setAttribute('data-text',"Save to MapMaking")

        element.innerHTML = `
        <div class="mwstmm-settings-option" id="mwstmm-opt-save-loc"/>`;

        container.appendChild(element);
        setTimeout(() => {
            if(document.querySelector('.TrU0dc.NUqjXc')){
                element.style.right='1rem'
                element.style.top='4rem'
            }}, 100)

        createSettingsButtonSummaryEvents();
    }

    function parseMeta(data) {
        const pathRegex = /@([^,]+),([^,]+),(\d+)a,([^y]+)y,([^h]+)h,([^t]+)t/;
        const urlObj = new URL(window.location.href);
        const path = urlObj.pathname;
        const pathMatch = path.match(pathRegex);
        const tags=[]

        var heading = pathMatch ? pathMatch[5] : null;
        var t = pathMatch ? pathMatch[6] : null;

        const panoId=data[1][0][1][1]
        const lat = data[1][0][5][0][1][0][2];
        const lng = data[1][0][5][0][1][0][3];
        const year = data[1][0][6][7][0];
        const month = data[1][0][6][7][1];
        const tilesize = data[1][0][2][2][0];
        if(!heading) heading=data[1][0][5][0][1][2]
        if(!t) t=90
        const date = new Date(year, month - 1);
        const formattedDate = date.toLocaleString('default', { month: 'short', year: 'numeric' });
        tags.push(formattedDate)
        let region, road, country;

        try {
            country = data[1][0][5][0][1][4];
            if (['TW', 'HK', 'MO'].includes(country)) {
                country = 'CN';
            }
            if(country) tags.push(country)
        } catch (e) {
            country = null;
        }

        try {
            const address = data[1][0][3][2][1][0];
            const parts = address.split(',')
            if(parts.length > 1){
                region = parts[parts.length-1].trim();
            } else {
                region = address;
            }
            if(region)tags.push(region)
        } catch (e) {
            try{
                const address=data[1][0][3][2][0][0]
                const parts = address.split(',')
                if(parts.length > 1){
                    region = parts[parts.length-1].trim();
                } else {
                    region = address;
                }
                if(region)tags.push(region)}
            catch(e){}
        }

        try {
            road = data[1][0][5][0][12][0][0][0][2][0];
            if(road) tags.push(road)
        } catch (e) {
            road = null;
        }



        const generation = getGeneration(tilesize, country, lat, date);
        if(generation) tags.push(generation)

        return {
            lat,
            lng,
            panoId,
            heading:parseFloat(heading),
            pitch:parseFloat(t)-90,
            zoom:0,
            tags
        }
    }

    function getGeneration(tilesize, country, lat, date) {
        if (tilesize) {
            if (tilesize === 1664) {
                return 'Gen1';
            } else if (tilesize === 6656) {
                const dateStr = date.toISOString().slice(0, 7);
                const gen2Countries = ['AU', 'BR', 'CA', 'CL', 'JP', 'GB', 'IE', 'NZ', 'MX', 'RU', 'US', 'IT', 'DK', 'GR', 'RO',
                                       'PL', 'CZ', 'CH', 'SE', 'FI', 'BE', 'LU', 'NL', 'ZA', 'SG', 'TW', 'HK', 'MO', 'MC', 'SM',
                                       'AD', 'IM', 'JE', 'FR', 'DE', 'ES', 'PT', 'SJ'];

                const gen3Dates = {
                    'BD': '2021-04', 'EC': '2022-03', 'FI': '2020-09', 'IN': '2021-10', 'LK': '2021-02', 'KH': '2022-10',
                    'LB': '2021-05', 'NG': '2021-06', 'ST': null, 'US': '2019-01'
                };

                if (dateStr >= gen3Dates[country]) {
                    if (country === 'US' && lat > 52) return 'Shitcam';
                    return 'Shitcam';
                }
                if (gen2Countries.includes(country)) return 'Gen2/3';
                return 'Gen3';
            } else if (tilesize === 8192) {
                return 'Gen4';
            }
        }
    }

    function haversine(lat1, lng1, lat2, lng2) {
        const R = 6371;
        const toRad = Math.PI / 180;
        const φ1 = lat1 * toRad;
        const φ2 = lat2 * toRad;
        const Δφ = (lat2 - lat1) * toRad;
        const Δλ = (lng2 - lng1) * toRad;
        const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
              Math.cos(φ1) * Math.cos(φ2) *
              Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
        const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

        return R * c;
    }

    async function seekDrivingEnd(data,timeRange){
        try{
            const startPano=data[1][0][1][1]
            const startLat=data[1][0][5][0][1][0][2]
            const startLng=data[1][0][5][0][1][0][3]
            const step1=data[1][0][5][0][3][0][1][0][1]
            const metaData = await UE('GetMetadata', step1);
            if(metaData&&metaData.length>1){
                const linkPano=metaData[1][0][5][0][3][0][1][0][1]===startPano?metaData[1][0][5][0][3][0][2]:metaData[1][0][5][0][3][0][1]
                const step2=linkPano[0][1]
                const metaData_ = await UE('GetMetadata', step2);
                if(metaData_){
                    const linkPano=metaData_[1][0][5][0][3][0][1][0][1]===step2?metaData_[1][0][5][0][3][0][2]:metaData_[1][0][5][0][3][0][1]
                    const step3=linkPano[0][1]
                    const lat_=linkPano[2][0][2]
                    const lng_=linkPano[2][0][3]
                    const captureTime = await binarySearch({"lat":lat_,"lng":lng_},timeRange.startDate,timeRange.endDate,step3,1);
                    return {time:captureTime,lat:lat_,lng:lng_}
                    }
            }
        }
        catch(e){
            console.log('Failed to seek pano'+e)
            return null
        }
    }

    async function getLOCATION(){
        const currentUrl = window.location.href;
        var panoId=extractParams(currentUrl).panoId;
        const metaData = await UE('GetMetadata', panoId);
        if(metaData) LOCATION = parseMeta(metaData)

    }

    function createSettingsButtonSummaryEvents() {
        document.getElementById('mwstmm-main').addEventListener('click', async () => {
            await getLOCATION()
            clickedMapButton();
        });
    }

    function addResultButton(location, item) {
        const btn = document.createElement('div');
        btn.className = `mwstmm-settings-option`;
        btn.textContent = `SAVE`;

        btn.addEventListener('click', () => {
            LOCATION = location;
            clickedMapButton();
        });

        item.appendChild(btn);
    }

    async function UE(t, e, s, d,r) {
        try {
            const url = `https://maps.googleapis.com/$rpc/google.internal.maps.mapsjs.v1.MapsJsInternalService/${t}`;
            let payload = createPayload(t, e,s,d,r);

            const response = await fetch(url, {
                method: "POST",
                headers: {
                    "content-type": "application/json+protobuf",
                    "x-user-agent": "grpc-web-javascript/0.1"
                },
                body: payload,
                mode: "cors",
                credentials: "omit"
            });

            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            } else {
                return await response.json();
            }
        } catch (error) {
            console.error(`There was a problem with the UE function: ${error.message}`);
        }
    }

    function createPayload(mode,coorData,s,d,r) {
        var payload;
        if (mode === 'GetMetadata') {
            const length=coorData.length
            if (length>22){
                type=10
            }
            else type=2
            payload = [["apiv3",null,null,null,"US",null,null,null,null,null,[[0]]],["en","US"],[[[type,coorData]]],[[1,2,3,4,8,6]]];
        }
        else if (mode === 'SingleImageSearch') {
            payload=[["apiv3"],[[null,null,parseFloat(coorData.lat),parseFloat(coorData.lng)],r],[[null,null,null,null,null,null,null,null,null,null,[s,d]],null,null,null,null,null,null,null,[2],null,[[[type,true,2]]]],[[2,6]]]}
        else {
            throw new Error("Invalid mode!");
        }
        return JSON.stringify(payload);
    }

    async function binarySearch(c, start,end,panoId,accuracy) {
        let capture
        let response
        if(!accuracy)accuracy=ACCURACY
        while( (end - start > accuracy)) {
            let mid=Math.round((start + end)/2) ;
            response = await UE("SingleImageSearch", c, start,end,30);
            if (response&&response.length>1){
                end=mid
                /*if (response[1][1][1]==panoId){
                    end=mid
                }
                else{
                    start=end
                    end+=(start-mid)}*/
            }
            else {
                start=end
                end+=(start-mid)}
            capture=Math.round((start + end)/2)
        }
        return capture
    }

    async function downloadPanoramaImage(panoId, fileName,panoramaWidth,panoramaHeight) {
        return new Promise(async (resolve, reject) => {
            try {
                const imageUrl = `https://streetviewpixels-pa.googleapis.com/v1/tile?cb_client=apiv3&panoid=${panoId}&output=tile&zoom=${zoomLevel}&nbt=0&fover=2`;
                const tileWidth = 512;
                const tileHeight = 512;
                const zoomTiles=[2,4,8,16,32]

                const tilesPerRow = Math.min(Math.ceil(panoramaWidth / tileWidth),zoomTiles[zoomLevel-1]);
                const tilesPerColumn = Math.min(Math.ceil(panoramaHeight / tileHeight),zoomTiles[zoomLevel-1]/2);

                const canvasWidth = tilesPerRow * tileWidth;
                const canvasHeight = tilesPerColumn * tileHeight;

                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d');
                canvas.width = canvasWidth;
                canvas.height = canvasHeight;

                const loadTile = (x, y) => {
                    return new Promise(async (resolveTile) => {
                        let tile;
                        const tileUrl = `${imageUrl}&x=${x}&y=${y}`;
                        try {
                            tile = await loadImage(tileUrl);
                            ctx.drawImage(tile, x * tileWidth, y * tileHeight, tileWidth, tileHeight);
                            resolveTile();
                        } catch (error) {
                            console.error(`Error loading tile at ${x},${y}:`, error);
                            resolveTile();
                        }
                    });
                };

                let tilePromises = [];
                for (let y = 0; y < tilesPerColumn; y++) {
                    for (let x = 0; x < tilesPerRow; x++) {
                        tilePromises.push(loadTile(x, y));
                    }
                }

                await Promise.all(tilePromises);

                canvas.toBlob(blob => {
                    const url = window.URL.createObjectURL(blob);
                    const a = document.createElement('a');
                    a.href = url;
                    a.download = fileName;
                    document.body.appendChild(a);
                    a.click();
                    document.body.removeChild(a);
                    window.URL.revokeObjectURL(url);
                    resolve();
                }, 'image/jpeg');
            } catch (error) {
                Swal.fire('Error!', error.toString(),'error');
                reject(error);
            }
        });
    }

    async function getElevation(lat, lng) {
        const url = `https://api.open-meteo.com/v1/elevation?latitude=${lat}&longitude=${lng}`;

        try {
            const response = await fetch(url);

            if (!response.ok) {
                console.error(`HTTP error! Status: ${response.status}`);
                return null
            }

            const data = await response.json();

            const altitude = data.elevation;
            if(altitude) return altitude[0]
            else return null

        } catch (error) {
            console.error('Error fetching elevation data:', error);
            return null
        }
    }

    async function loadImage(url) {
        return new Promise((resolve, reject) => {
            const img = new Image();
            img.crossOrigin = 'Anonymous';
            img.onload = () => resolve(img);
            img.onerror = () => reject(new Error(`Failed to load image from ${url}`));
            img.src = url;
        });
    }

    function monthToTimestamp(m) {

        const [year, month] = m

        const startDate =Math.round( new Date(year, month-1,0).getTime()/1000);

        const endDate =Math.round(new Date(year, month, 2).getTime()/1000)

        return { startDate, endDate };
    }

    async function getLocal(coord, timestamp) {
        const systemTimezoneOffset = -new Date().getTimezoneOffset() * 60;

        try {
            var offset_hours, offset
            const timezone=await GeoTZ.find(coord[0],coord[1])
            try{
                offset = await GeoTZ.toOffset(timezone);}
            catch(error){
                offset = await GeoTZ.toOffset(timezone[0]);}


            if(offset){
                offset_hours=parseInt(offset/60)
            }
            else if (offset===0) offset_hours=0

            const offsetDiff = systemTimezoneOffset -offset_hours*3600;
            const convertedTimestamp = timestamp -offsetDiff;
            return convertedTimestamp;
        } catch (e) {
            try {
                const [lat, lng] = coord;
                const url = `https://api.wheretheiss.at/v1/coordinates/${lat},${lng}`;

                const response = await fetch(url);
                if (!response.ok) {
                    throw new Error("Request failed: " + response.statusText);
                }
                const data = await response.json();
                const targetTimezoneOffset = data.offset * 3600;
                const offsetDiff = systemTimezoneOffset - targetTimezoneOffset;
                const convertedTimestamp = Math.round(timestamp - offsetDiff);
                return convertedTimestamp;
            }
            catch (e){
                console.log('Failed to get timezone data'+e)
            }
        }
    }

    function getMoonPhaseIcon(dayOfMonth) {
        const cycleDays = 29.53;
        const phaseIndex = Math.floor((dayOfMonth % cycleDays) / (cycleDays / moon_phase.length));
        return moon_phase[phaseIndex];
    }

    function formatTimestamp(timestamp) {
        var date_text,time_text
        const date = new Date(timestamp * 1000);
        const year = date.getFullYear();
        const month = String(date.getMonth() + 1).padStart(2, '0');
        const day = String(date.getDate()).padStart(2, '0');
        const hours = String(date.getHours()).padStart(2, '0');
        const minutes = String(date.getMinutes()).padStart(2, '0');
        const seconds = String(date.getSeconds()).padStart(2, '0');

        if (DATE_FORMAT===2) date_text=`${year}/${month}/${day}`
        else if (DATE_FORMAT===3) date_text=`${day}/${month}/${year}`
        else if (DATE_FORMAT===4) date_text=`${month}/${day}/${year}`
        else if (DATE_FORMAT===5) date_text=`${months[parseInt(month)]} ${parseInt(day)}, ${year}`
        else if (DATE_FORMAT===6) date_text=`${parseInt(day)} ${months[parseInt(month)]}, ${year}`
        else if (DATE_FORMAT===7) {
            const lunarDate=chineseLunar.solarToLunar(date)
            date_text = `${year}-${month}-${day} ${chineseLunar.format(lunarDate, 'MD')} ${getMoonPhaseIcon(lunarDate.day)}`;}
        else date_text=`${year}-${month}-${day}` ;

        if(TIME_FORMAT===1) time_text=`${hours}:${minutes}:${seconds}`
        else{
            const period = parseInt(hours) >= 12 ? 'pm' : 'am';

            var _hours = parseInt(hours) % 12 || 12;
            _hours = String(_hours).padStart(2, '0');
            time_text= `${_hours}:${minutes}:${seconds} ${period}`;
        }

        if(!DATE_FORMAT)return date.toLocaleString().replace('T', ' ');
        else return `${date_text} ${time_text}`;

    }

    function formatLatLng(lat, lng) {

        const latDirection = lat >= 0 ? 'N' : 'S';
        const latAbs = Math.abs(lat).toFixed(4);
        const latStr = `${latAbs}°${latDirection}`;


        const lngDirection = lng >= 0 ? 'E' : 'W';
        const lngAbs = Math.abs(lng).toFixed(4);
        const lngStr = `${lngAbs}°${lngDirection}`;

        return `${latStr}, ${lngStr}`;
    }

    async function addCustomButton() {
        const navigationDiv = document.querySelector("[role='navigation']");
        if (!navigationDiv) return;


        if (!detectButton||!downloadButton){


            detectButton = document.createElement("button");
            downloadButton = document.createElement("button");
            const symbol=document.querySelector("[jsaction='titlecard.settings']")
            const buttonContainer = symbol.parentNode;
            if(symbol){
                const spotlight=document.querySelector("[jsaction='titlecard.spotlight']")
                const rect_=spotlight.getBoundingClientRect()
                if(rect_.top){
                    const rect = symbol.getBoundingClientRect();
                    var absoluteTop = rect.top
                    var absoluteLeft = rect.left}
            }

            detectButton.id = 'detect-button';
            detectButton.style.marginTop = '1px';
            setupButton(detectButton, svgUrl,absoluteTop,absoluteLeft);

            downloadButton.id = 'download-button';
            setupButton(downloadButton, iconUrl,absoluteTop,absoluteLeft);
            if(absoluteLeft&&absoluteTop) downloadButton.style.marginLeft = '26px';
            else downloadButton.style.marginLeft='1px'

            addButtonHoverEffect(detectButton, svg_Url, svgUrl);
            addButtonHoverEffect(downloadButton, icon_Url, iconUrl)
            downloadButton.addEventListener("click",async function(){
                const { value: zoom, dismiss: inputDismiss } = await Swal.fire({
                    title: 'Image Quality',
                    html:
                    '<select id="zoom-select" class="swal2-input" style="width:180px; height:40px; font-size:16px;white-space:prewrap">' +
                    '<option value="1">1 (100KB~500KB)</option>' +
                    '<option value="2">2 (500KB~1MB)</option>' +
                    '<option value="3">3 (1MB~4MB)</option>' +
                    '<option value="4">4 (4MB~8MB)</option>' +
                    '<option value="5">5 (8MB~15MB)</option>' +
                    '</select>',
                    icon: 'question',
                    showCancelButton: true,
                    showCloseButton: true,
                    allowOutsideClick: false,
                    confirmButtonColor: '#3085d6',
                    cancelButtonColor: '#d33',
                    confirmButtonText: 'Yes',
                    cancelButtonText: 'Cancel',
                    preConfirm: () => {
                        return document.getElementById('zoom-select').value;
                    }
                });
                if (zoom){
                    zoomLevel=parseInt(zoom)
                    const currentUrl = window.location.href;
                    var panoId=extractParams(currentUrl).panoId;
                    var panoDate,lat,lng
                    const metaData = await UE('GetMetadata', panoId);
                    if (!metaData) {
                        console.error('Failed to get metadata');
                        return;
                    }
                    try{
                        w=parseInt(metaData[1][0][2][2][1])
                        h=parseInt(metaData[1][0][2][2][0])
                        panoDate=metaData[1][0][6][7]
                        lat=metaData[1][0][5][0][1][0][2]
                        lng=metaData[1][0][5][0][1][0][3]

                    }
                    catch (error){
                        try{
                            w=parseInt(metaData[1][2][2][1])
                            h=parseInt(metaData[1][2][2][0])
                            panoDate=metaData[1][6][7]
                            lat=metaData[1][5][0][1][0][2]
                            lng=metaData[1][5][0][1][0][3]
                        }
                        catch (error){
                            console.log(error)
                            return
                        }
                    }
                    if(w&&h){
                        const gpsTag=formatLatLng(lat,lng)
                        var timeTag
                        if(panoId===capturePano&&formattedTime) timeTag=formattedTime
                        else timeTag=`${panoDate[0]}-${panoDate[1]}`
                        const fileName = `${gpsTag}(${timeTag}).jpg`;
                        const swal = Swal.fire({
                            title: 'Downloading',
                            text: 'Please wait...',
                            allowOutsideClick: false,
                            allowEscapeKey: false,
                            showConfirmButton: false,
                            didOpen: () => {
                                Swal.showLoading();
                            }
                        });
                        await downloadPanoramaImage(panoId, fileName,w,h);
                        swal.close()
                        Swal.fire('Success!','Download completed', 'success');
                    }

                }

            })
            detectButton.addEventListener("click",async function() {
                let dateSpan = document.querySelector('span.lchoPb');
                if (!dateSpan){
                    dateSpan = document.querySelector('span.mqX5ad');
                    if(!dateSpan){
                        dateSpan = document.querySelector('div.mqX5ad');
                        if(!dateSpan)
                        {
                            dateSpan = document.querySelector('div.lchoPb');
                        }
                    }
                }
                const logoSpan=document.querySelector('span.ilzTS')
                const logoDiv=document.querySelector('div.p4x6kc')
                if (dateSpan){
                    dateSpan.innerHTML='loading...'
                }

                const currentUrl = window.location.href;
                var altitude
                var panoId=extractParams(currentUrl).panoId;
                var lat=extractParams(currentUrl).lat
                var lng=extractParams(currentUrl).lng
                if (panoId.length>22) type=3
                try {

                    const metaData = await UE('GetMetadata', panoId);
                    if (!metaData) {
                        console.error('Failed to get metadata');
                        return;
                    }

                    var panoDate
                    try {
                        panoDate = metaData[1][0][6][7];
                        capturePano=metaData[1][0][1][1]
                        altitude=metaData[1][0][5][0][1][1][0]
                    } catch (error) {
                        try{
                            panoDate = metaData[1][0][6][7];
                            capturePano=metaData[1][0][1][1]}
                        catch(error){
                            dateSpan.textContent='unknown'
                            console.error('Failed to parse metadata')
                            return
                        }

                    }
                    /*if (logoSpan){
                        //GM_setClipboard(`${altitude},"${panoId}"`,'text')
                    }*/
                    const timeRange = monthToTimestamp(panoDate);
                    if (!timeRange) {
                        console.error('Failed to convert panoDate to timestamp');
                        return;
                    }

                    try {
                        const [captureTime, drivingEnd] = await Promise.all([
                            binarySearch({"lat": lat, "lng": lng}, timeRange.startDate, timeRange.endDate, panoId),
                            seekDrivingEnd(metaData, timeRange)
                        ]);
                        if (!captureTime) {
                            console.error('Failed to get capture time');
                            return;
                        }
                        const exactTime=await getLocal([lat,lng],captureTime)

                        if(!exactTime){
                            console.error('Failed to get exact time');
                        }
                        formattedTime=formatTimestamp(exactTime)
                        if(dateSpan){
                            dateSpan.textContent = formattedTime;
                        }
                        if(logoSpan && drivingEnd){
                            const timeConsume=Math.abs(captureTime-drivingEnd.time)
                            var distance = haversine(lat, lng, drivingEnd.lat, drivingEnd.lng)
                            if(DISTANCE_UNITS==='MILES')distance=distance * 0.621371
                            if (timeConsume != 0) {
                                const timeInHours = timeConsume / 3600;
                                const avgSpeed=distance / timeInHours;
                                if(avgSpeed) {
                                    logoSpan.textContent=!avgSpeed?'? km/h':`${Math.round(avgSpeed*100)/100} km/h `
                                    logoDiv.style.backgroundImage=`url(https://cdn.discordapp.com/emojis/776219536936402984.webp?size=100)`
                                }
                            }
                            else{
                                logoSpan.textContent='? km/h'}
                            if(!altitude) altitude=await getElevation(lat,lng)
                            logoSpan.textContent+=altitude ==null?'unknown':` ${altitude>50?mountain:wave} ${Math.round(altitude*100)/100}m`
                        }
                    } catch (error) {
                        console.log(error);
                    }
                } catch (error) {
                    console.error(error);
                }
            })

            buttonContainer.appendChild(downloadButton)
            buttonContainer.appendChild(detectButton)
        }
        else{
            if(document.getElementById('detect-button')&&document.getElementById('download-button')) return
            else {
                const symbol=document.querySelector("[jsaction='titlecard.settings']")
                const buttonContainer = symbol.parentNode;
                buttonContainer.appendChild(downloadButton)
                buttonContainer.appendChild(detectButton)
            }
        }


    }

    function checkPosition(button,backgroundUrl) {
        const symbol=document.querySelector("[jsaction='titlecard.settings']")
        if(!symbol) return
        const rect = symbol.getBoundingClientRect();
        const spotlight=document.querySelector("[jsaction='titlecard.spotlight']")
        const rect_=spotlight.getBoundingClientRect()
        var absoluteTop = rect.top
        var absoluteLeft = rect.left
        if(rect_.top){
            if(absoluteTop&&absoluteLeft){
                button.style.position='fixed'
                button.style.top=`${absoluteTop+40}px`
                button.style.left=`${absoluteLeft-25}px`}}
        else{
            button.style.position=null
            button.style.top=null
            button.style.left=null
        }
    }

    function setupButton(button, backgroundUrl,top,left) {
        button.style.backgroundImage = `url(${backgroundUrl})`;
        button.style.backgroundSize = 'cover';
        button.style.backgroundPosition = 'center';
        button.style.display = 'block';
        button.style.width = '24px';
        button.style.height = '24px';
        button.style.fontSize = '12px';
        button.style.borderRadius = '10px';
        button.style.cursor = 'pointer';
        button.style.backgroundColor = 'transparent';
        if(top&&left){
            button.style.position='fixed'
            button.style.top=`${top+40}px`
            button.style.left=`${left-25}px`
            setInterval(function (){checkPosition(button,backgroundUrl)}, 100)
        }
    }

    function calculateFOV(zoom) {
        const pi = Math.PI;
        const argument = (3 / 4) * Math.pow(2, 1 - zoom);
        const radians = Math.atan(argument);
        const degrees = (360 / pi) * radians;
        return degrees;
    }

    async function getShortUrl(pageUrl) {
        const url = 'https://www.google.com/maps/rpc/shorturl';
        const panoId=extractParams(pageUrl).panoId;
        const urlObj = new URL(pageUrl);
        const path = urlObj.pathname;
        const pathRegex = /@([^,]+),([^,]+),(\d+)a,([^y]+)y,([^h]+)h,([^t]+)t/;
        const pathMatch = path.match(pathRegex);
        const lat = pathMatch ? pathMatch[1] : null;
        const lng = pathMatch ? pathMatch[2] : null;
        const a=pathMatch ? pathMatch[3] : null;
        const y = pathMatch ? pathMatch[4] : null;
        const h = pathMatch ? pathMatch[5] : null;
        const t = pathMatch ? pathMatch[6] : null;

        const pb = `!1shttps://www.google.com/maps/@${lat},${lng},${a}a,${y}y,${h}h,${t}t/data=*213m5*211e1*213m3*211s${panoId}*212e0*216shttps%3A%2F%2Fstreetviewpixels-pa.googleapis.com%2Fv1%2Fthumbnail%3Fpanoid%3D${panoId}%26cb_client%3Dmaps_sv.share%26w%3D900%26h%3D600%26yaw%3D${h}%26pitch%3D${t-90}%26thumbfov%3D100*217i16384*218i8192?coh=205410&entry=tts!2m1!7e81!6b1`;


        const params = new URLSearchParams({
            authuser: '0',
            hl: 'en',
            gl: 'us',
            pb: pb
        }).toString();

        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: `${url}?${params}`,
                onload: function (response) {
                    if (response.status >= 200 && response.status < 300) {
                        try {
                            const text = response.responseText;
                            const match = text.match(/"([^"]+)"/);
                            if (match && match[1]) {
                                resolve(match[1]);
                            } else {
                                reject('No URL found.');
                            }
                        } catch (error) {
                            reject('Failed to parse response: ' + error);
                        }
                    } else {
                        reject('Request failed with status: ' + response.status);
                    }
                },
                onerror: function (error) {
                    reject('Request error: ' + error);
                }
            });
        });
    }

    function addButtonHoverEffect(button, hoverImageUrl, defaultImageUrl) {
        button.addEventListener("mouseenter", function(event) {
            button.style.backgroundImage = `url(${hoverImageUrl})`;
        });
        button.addEventListener("mouseleave", function(event) {
            button.style.backgroundImage = `url(${defaultImageUrl})`;
        });
    }
    let pageLoaded = false;

    function onPageLoad() {
        if (pageLoaded) return;
        pageLoaded = true;

        const sceneFooter = document.getElementsByClassName('scene-footer')[0];

        if (!sceneFooter) {
            console.error('scene-footer element not found');
            return;
        }

        const observer = new MutationObserver(function (mutationsList) {
            const navigationDiv = document.querySelector("[role='navigation']");

            if (navigationDiv) {
                addCustomButton();
                addSettingsButtonsToPage();
            } else {
                const element = document.getElementById('mwstmm-main');
                if (element) element.remove();
            }
        });

        const config = { childList: true, subtree: true, attributes: true };

        observer.observe(sceneFooter, config);
    }

    if (!pageLoaded) {
        window.addEventListener('load', onPageLoad);
    }

    function getMap(){
        const element = document.getElementsByClassName("iBPHvd widget-scene")[0]
        if(element){
            const keys = Object.keys(element)
            console.log(keys)
            const key = keys.find(key => key.startsWith("__cdn"))
            const props = element[key]
            console.log(props)
        }
    }

    let onKeyDown = async (e) => {
        if (e.target.tagName === 'TEXTAREA') {
            return;
        }
        if ((e.ctrlKey || e.metaKey)&&(e.key === 'z' || e.key === 'Z')) {
            if(!previousMapId) return
            e.stopImmediatePropagation();
            await getLOCATION()
            importLocations(previousMapId, [{
                id: -1,
                location: {lat: LOCATION.lat, lng: LOCATION.lng},
                panoId: LOCATION.panoId ?? null,
                heading: LOCATION.heading ?? 90,
                pitch: LOCATION.pitch ?? 0,
                zoom: LOCATION.zoom === 0 ? null : LOCATION.zoom,
                tags: LOCATION.tags,
                flags: LOCATION.panoId ? 1 : 0
            }]);
            Swal.fire({
                title: 'Import Succeed',
                text: 'Locations has been added into your map!',
                icon: 'success',
                timer: 1000,
                showConfirmButton: false,
            });
        }
        if ((e.ctrlKey || e.metaKey)&&(e.key === 'x' || e.key === 'X')) {
            e.stopImmediatePropagation();
            let pageUrl = window.location.href;
            const currentLink=await getShortUrl(pageUrl)
            GM_setClipboard(currentLink, 'text');
            Swal.fire({
                title: 'Copy Succeed',
                text: 'Short Link has been pasted to your clipboard!',
                icon: 'success',
                timer: 1000,
                showConfirmButton: false,
            });
        }
    }

    document.addEventListener("keydown", onKeyDown);
})();