GeoGuessr Quick-Moving Shortcut

create shortcut for qucik moving in geoguessr game

// ==UserScript==
// @name         GeoGuessr Quick-Moving Shortcut
// @namespace    https://greasyfork.org/users/1179204
// @version      0.0.8
// @author       KaKa
// @description  create shortcut for qucik moving in geoguessr game
// @license      MIT
// @match        *://*.geoguessr.com/*
// @icon         https://www.svgrepo.com/show/509212/rocket.svg
// ==/UserScript==

/* ------------------------------------------------------------------------------- */
/* ----- KEYBOARD SHORTCUTS (MUST Refresh PAGE FOR CHANGES TO TAKE EFFECT) -------- */
/* ------------------------------------------------------------------------------- */

const KEY_BIND = {
    // Single key
    FORWARD: '3',

    BACKWARD: '4',
};

const STEP= 0.1 // kilo metres

/* ############################################################################### */
/* ##### DON'T MODIFY ANYTHING BELOW HERE UNLESS YOU KNOW WHAT YOU ARE DOING ##### */
/* ############################################################################### */


let service;
let isApplied;

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

const svg=`<svg fill="#ffffff" width="16px" height="16px" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg" stroke="#ffffff"><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="M11.5 0c-.654 0-.65 1 0 1h6.842c.72 0 .73.408.554.77l-3.84 7.5c-.172.334.07.73.444.73h7.072c.15 0 .323.093.39.248.07.155.095.443-.288.95L10.33 27.57l1.664-9.988c.05-.305-.185-.583-.494-.582H7.54c-.557 0-.6-.164-.505-.934l1.96-14.5c.09-.66-.903-.794-.99-.132l-1.962 14.5c-.075.556-.06 1.048.203 1.46.264.413.768.606 1.293.606h3.368L9.006 29.418c-.086.516.578.8.892.383l13.575-18c.524-.694.643-1.406.406-1.95-.24-.544-.77-.85-1.308-.85h-6.254l3.467-6.77c.188-.368.316-.856.12-1.36C19.707.363 19.12 0 18.34 0z"></path></g></svg>`
const svgUrl=svgToUrl(svg)
function getViewer(){
    try{
        const container= document.querySelector('div[data-qa="panorama"]');
        const keys = Object.keys(container)
        const key = keys.find(key => key.startsWith("__reactFiber"))
        const props = container[key]
        return props.return.updateQueue.lastEffect.deps[0];
    }
    catch(e){
        console.error('Error finding pano viewer: '+e);
    }
}

async function getPanorama(lat, lng){
    if (google){
        if (!service) service = new google.maps.StreetViewService();
        return new Promise(resolve => {
            service.getPanorama({
                location: { lat, lng },
                radius: STEP * 500,
                source: google.maps.StreetViewSource.GOOGLE
            }, (data, status) => {
                if (status === 'OK') resolve(data);
                else resolve(null);
            });
        });
    } else {
        console.error('Google StreetView Service is not prepared!');
        return null;
    }
}


function getLocation(viewer){
    return {
        lat: viewer.location.latLng.lat(),
        lng: viewer.location.latLng.lng(),
        heading: viewer.getPov().heading
    };
}

async function moveStreetView(direction) {
    const viewer = getViewer();
    if (!viewer) return;

    let { lat, lng, heading } = getLocation(viewer);
    const distance = direction === 'forward' ? STEP / 100 : -STEP / 100;

    const radHeading = heading * (Math.PI / 180);
    const radLeftHeading = (heading - 45) * (Math.PI / 180);
    const radRightHeading = (heading + 45) * (Math.PI / 180);

    let mainLat = lat + Math.cos(radHeading) * distance;
    let mainLng = lng + Math.sin(radHeading) * distance;

    let leftLat = lat + Math.cos(radLeftHeading) * distance;
    let leftLng = lng + Math.sin(radLeftHeading) * distance;

    let rightLat = lat + Math.cos(radRightHeading) * distance;
    let rightLng = lng + Math.sin(radRightHeading) * distance;

    let [panoMain, panoLeft, panoRight] = await Promise.all([
        getPanorama(mainLat, mainLng),
        getPanorama(leftLat, leftLng),
        getPanorama(rightLat, rightLng)
    ]);

    let newLat = mainLat, newLng = mainLng;

    if (panoMain) {
        newLat = panoMain.location.latLng.lat();
        newLng = panoMain.location.latLng.lng();
    } else if (panoLeft) {
        newLat = panoLeft.location.latLng.lat();
        newLng = panoLeft.location.latLng.lng();
    } else if (panoRight) {
        newLat = panoRight.location.latLng.lat();
        newLng = panoRight.location.latLng.lng();
    }
    else{
        newLat = lat
        newLng = lng
    }
    viewer.setPosition({
        lat: newLat,
        lng: newLng
    });
}


let onKeyDown = (e) =>{
    const container= document.querySelector('div[data-qa="panorama"]');
    if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable||!container||!isApplied) {
        return;
    }
    e.stopPropagation();
    if (e.key === KEY_BIND.FORWARD) moveStreetView('forward');
    if (e.key === KEY_BIND.BACKWARD) moveStreetView('backward');
}

    document.addEventListener("keydown", onKeyDown);
if (!window.location.href.includes('duel')) {
    const observer = new MutationObserver((mutationsList, observer) => {
        const originalElements = document.querySelectorAll(".styles_control__Pa4Ta");

        if (originalElements.length>2 && !document.getElementById('quick-move-button')) {
            const targetElement = originalElements[originalElements.length - 1];
            const clonedElement = targetElement.cloneNode(true);
            clonedElement.id='quick-move-button'
            const parent = targetElement.parentNode;
            parent.insertBefore(clonedElement, targetElement);
            const tooltip = clonedElement.querySelector(".tooltip_tooltip__3D6bz");
            if (tooltip) {
                tooltip.textContent = "Enable quick moving";
                tooltip.style.transition="0.3s"
                const arrow = document.createElement("div");
                arrow.classList.add("tooltip_arrow__LJ1of");
                tooltip.appendChild(arrow);

                const imgElement = clonedElement.querySelector("img");
                const buttonElement = clonedElement.querySelector("button");
                if (imgElement) {
                    imgElement.src = svgUrl;
                }
                if(isApplied){
                    tooltip.textContent = "Disable Quick Moving";
                    buttonElement.style.outline = '2px solid #e6a014';
                }
                clonedElement.addEventListener("mouseover", () => {

                    tooltip.style.visibility = "visible";
                    tooltip.style.opacity = "1";
                    arrow.style.opcaity="1"
                    tooltip.style.transform="translateY(-50%) scale(1)"
                });

                clonedElement.addEventListener("mouseout", () => {
                    tooltip.style.visibility = "hidden";
                    tooltip.style.opacity = "0";
                    tooltip.style.transform="translateY(-50%) scale(0)"
                });

                clonedElement.addEventListener("click",()=>{
                    if(!isApplied){
                        tooltip.textContent = "Disable Quick Moving";
                        buttonElement.style.outline = '2px solid #e6a014';
                    }
                    else {
                        tooltip.textContent = "Enable Quick Moving";
                        buttonElement.style.outline='';

                    }
                    tooltip.appendChild(arrow);
                    isApplied=!isApplied
                })
            }
        }});
    observer.observe(document.body, { childList: true, subtree: true });
}