Greasy Fork

GeoGuessr Background Replacer

Replaces the background of the geoguessr pages with your own images

目前为 2023-06-02 提交的版本。查看 最新版本

// ==UserScript==
// @name         GeoGuessr Background Replacer
// @description  Replaces the background of the geoguessr pages with your own images
// @version      2.0
// @author       Tyow#3742
// @match        *://*.geoguessr.com/*
// @license      MIT
// @require      https://unpkg.com/@popperjs/[email protected]/dist/umd/popper.min.js
// @namespace    https://greasyfork.org/users/1011193
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @require      https://greasyfork.org/scripts/460322-geoguessr-styles-scan/code/Geoguessr%20Styles%20Scan.js?version=1151668
// ==/UserScript==

// Some code for popup adapted from blink script: https://greasyfork.org/en/scripts/438579-geoguessr-blink-mode

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

const guiHTMLHeader = `
<div id="backgroundReplacerPopupWrapper">
  <div id="backgroundReplacerSearchWrapper">
    <div id="backgroundReplacerSlantedRoot">
      <div id="backgroundReplacerSlantedStart"></div>
      <div id="backgroundReplacerInputWrapper">
        <div id="backgroundReplacerPopup" style="background: rgba(26, 26, 46, 0.9); padding: 15px; border-radius: 10px; max-height: 80vh; overflow-y: auto; width: 28em">
          <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px;">
            <span id="backgroundReplacerLabel1" style="margin: 0; padding-right: 6px;">Add Home Page image</span>
            <input type="url" id="homepageInput" name="homepage" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px">
          </div>
          <span>Home Page Images:</span>
          <div id="homePageImages" style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px; flex-direction: column"></div>
          <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px;">
            <span id="backgroundReplacerLabel2" style="margin: 0; padding-right: 6px;">Add Other Page Image</span>
            <input type="url" id="otherpagesInput" name="otherpages" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px">
          </div>
          <span>Other Pages Images:</span>
          <div id="otherPagesImages" style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px; flex-direction: column"></div>
        </div>
        <button style="width: 59.19px" id="backgroundReplacerToggle"><picture id="backgroundReplacerTogglePicture" style="justify-content: center"><img src="https://www.svgrepo.com/show/342899/wallpaper.svg" style="width: 15px; filter: brightness(0) invert(1); opacity: 60%;"></picture></button>
      </div>
      <div id="backgroundReplacerSlantedEnd"></div>
    </div>
  </div>
</div>
`
let homePageImageList = GM_getValue("homepageImages");
let otherImages = GM_getValue("otherImages");

// Defaults
if (homePageImageList == undefined) {
    homePageImageList = [
        "https://cdn.wallpapersafari.com/6/80/9ZbpYo.jpg",
        "https://cdn.wallpapersafari.com/25/72/dtkc16.jpg",
        "https://i.imgur.com/l9K9IOq.jpg",
    ];
    GM_setValue("homepageImages", homePageImageList);
}
if (otherImages == undefined) {
    otherImages = [
        "https://imgur.com/eK23SeH.jpg",
        "https://i.imgur.com/l9K9IOq.jpg"
    ];
    GM_setValue("otherImages", otherImages);
}

let hide = false;
let styles = GM_getValue("backgroundReplacerStyles");
if (!styles) {
    hide = true;
    styles = {};
}

let homePageImgURL;



const setHomePageImg = () => {
    if(homePageImageList.length) {
        homePageImgURL = homePageImageList[Math.floor((Math.random()*homePageImageList.length))];
    } else {
        homePageImgURL = "";
    }
}

setHomePageImg();

let otherPagesImgURL;

const setOtherImg = () => {
    if(otherImages.length) {
        otherPagesImgURL = otherImages[Math.floor((Math.random()*otherImages.length))];
    } else {
        otherPagesImgURL = "";
    }
}

setOtherImg();

let css = `.customBackground { bottom: 0;
display: block;
height: 100%;
object-fit: cover;
pointer-events: none;
position: fixed;
right: 0;
transition: .2s ease-in-out;
width: 100%;
}
.zindex {
  z-index: -1;
}
.deleteIcon {
    width: 25px;
    filter: brightness(0) invert(1);
    opacity: 60%;
}
.backgroundImage {
    width: 20em;
}
.deleteButton {
    width: 59.19px;
    margin-bottom: 8em;
}
.backgroundImageWrapper {
    display: flex;
    padding: .5em;
}
.deleteIconPicture {
   justifyContent:center;
}

`;
GM_addStyle(css);


const showPopup = (showButton, popup) => {
    popup.style.display = 'block';
    Popper.createPopper(showButton, popup, {
        placement: 'bottom',
        modifiers: [
            {
                name: 'offset',
                options: {
                    offset: [0, 10],
                },
            },
        ],
    });
}

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

const iterativeSetTimeout = async (func, delay, cond) => {
    while (!cond()) {
        await delay(delay);
        await func();
        delay *= 2;
    }
};

// Caching system for styles
// Basically, we have a browser stored styles object,
// which contains the most recent classNames found by scanStyles()
// This is what the script will immediately use upon loading,
// so that there's no pause in delivering the UI to the user
// But the script will also fire off this function
// which will use the above iterativeSetTimeout function to call scanStyles
// This is so there aren't a thousand calls in quick succession.
// Once all the classNames we're looking for are found,
// it will update the local storage and the ui with the (possibly) new classnames
const uploadDownloadStyles = async () => {
    await iterativeSetTimeout(scanStyles, 0.1, () => checkAllStylesFound(["header_item__",
                                                                          "quick-search_wrapper__",
                                                                          "slanted-wrapper_root__",
                                                                          "slanted-wrapper_variantGrayTransparent__",
                                                                          "slanted-wrapper_start__",
                                                                          "slanted-wrapper_right__",
                                                                          "quick-search_searchInputWrapper__",
                                                                          "slanted-wrapper_end__",
                                                                          "slanted-wrapper_right__",
                                                                          "quick-search_searchInputButton__",
                                                                          "game-options_optionLabel__",
                                                                          "game-options_optionLabel__",
                                                                          "quick-search_iconSection__",
                                                                          "quick-search_searchInputButton__"]) !== undefined);
    if (hide) {
        document.querySelector("#backgroundReplacerPopupWrapper").hidden = "";
    }
    styles["header_item__"] = cn("header_item__");
    styles["quick-search_wrapper__"] = cn("quick-search_wrapper__");
    styles["slanted-wrapper_root__"] = cn("slanted-wrapper_root__");
    styles["slanted-wrapper_variantGrayTransparent__"] = cn("slanted-wrapper_variantGrayTransparent__");
    styles["slanted-wrapper_start__"] = cn("slanted-wrapper_start__");
    styles["slanted-wrapper_right__"] = cn("slanted-wrapper_right__");
    styles["quick-search_searchInputWrapper__"] = cn("quick-search_searchInputWrapper__");
    styles["slanted-wrapper_end__"] = cn("slanted-wrapper_end__");
    styles["quick-search_searchInputButton__"] = cn("quick-search_searchInputButton__");
    styles["game-options_optionLabel__"] = cn("game-options_optionLabel__");
    styles["quick-search_iconSection__"] = cn("quick-search_iconSection__");
    styles["quick-search_searchInputButton__"] = cn("quick-search_searchInputButton__");
    GM_setValue("backgroundReplacerStyles", styles);
    setStyles()
}

const setStyles = () => {
    document.querySelector("#backgroundReplacerPopupWrapper").className = styles["header_item__"];
    document.querySelector("#backgroundReplacerSearchWrapper").className = styles["quick-search_wrapper__"];
    document.querySelector("#backgroundReplacerSlantedRoot").className = styles["slanted-wrapper_root__"]+ " " + styles["slanted-wrapper_variantGrayTransparent__"];
    document.querySelector("#backgroundReplacerSlantedStart").className = styles["slanted-wrapper_start__"]+ " " + styles["slanted-wrapper_right__"];
    document.querySelector("#backgroundReplacerInputWrapper").className = styles["quick-search_searchInputWrapper__"];
    document.querySelector("#backgroundReplacerSlantedEnd").className = styles["slanted-wrapper_end__"]+ " " + styles["slanted-wrapper_right__"];
    document.querySelector("#backgroundReplacerToggle").className = styles["quick-search_searchInputButton__"];
    document.querySelector("#backgroundReplacerLabel1").className = styles["game-options_optionLabel__"];
    document.querySelector("#backgroundReplacerLabel2").className = styles["game-options_optionLabel__"];
    document.querySelector("#backgroundReplacerTogglePicture").className = styles["quick-search_iconSection__"];
    document.querySelectorAll(".deleteButton").forEach(el => el.className = el.className + " " + cn("quick-search_searchInputButton__"));
}


const insertHeaderGui = async (header, gui) => {

    header.insertAdjacentHTML('afterbegin', gui);

    // Resolve class names
    if (hide) {
        document.querySelector("#backgroundReplacerPopupWrapper").hidden = "true"
    }

    scanStyles().then(() => uploadDownloadStyles());
    setStyles();



    const showButton = document.querySelector('#backgroundReplacerToggle');
    const popup = document.querySelector('#backgroundReplacerPopup');
    popup.style.display = 'none';

    document.addEventListener('click', (e) => {
        const target = e.target;
        if (target == popup || popup.contains(target) || !document.contains(target)) return;
        if (target.matches('#backgroundReplacerToggle, #backgroundReplacerToggle *')) {
            e.preventDefault();
            showPopup(showButton, popup);
        } else {
            popup.style.display = 'none';
        }

        if (document.querySelector('#enableScriptHeader')) {
            if (localStorage.getItem('blinkEnabled') === 'enabled') {
                document.querySelector('#enableScriptHeader').checked = true;
            }
        }
    });
}

// Global to track whether the most recent image insertion was done on homepage
let isHomePage = location.pathname == "/";

const insertBackground = (refresh=false) => {
    let inGame = false;
    let el = document.querySelector("[class^='background_wrapper']");
    if (!el) {
        inGame = true;
        el = document.querySelector("#__next");
        if (!el) return;
        // Because this element has multiple classes, we need to use a different selector
        const def = document.querySelector("[class*=in-game_backgroundDefault__]");
        let reg = /^in-game_backgroundDefault__/;
        if (def) {
            def.classList = Array.from(def.classList).filter(cl => !cl.match(reg));
        }
        const partyRoot = document.querySelector("[class^=party_root__]");
        if (partyRoot) {
            partyRoot.style.background = "none";
        }
        // Without this, you can see the background behind the map in a game summary

        // Purple color used by geoguessr, with .9 alpha
        const purple9 = "rgba(12 12 46 / .9)";
        // .7 alpha
        const purple7 = "rgba(12 12 46 / .7)";
        const gameSummary = document.querySelector("[class^=game-summary_container__");
        if (gameSummary) {
            gameSummary.style.opacity = "1";
            gameSummary.style.backgroundColor = purple9;
        }
        const header = document.querySelector("[class^=game-summary_playedRoundsHeader__");
        if (header) {
            header.style.backgroundColor = purple7;
        }

    }
    // We only want the zindex = -1 to exist in game settings, on other pages it's detrimental
    let img = document.querySelector('.customBackground');
    if (refresh) {
        img.remove();
        img = document.querySelector('.customBackground');
    }
    if (img) {
        if (!inGame) {
            img.classList = Array.from(img.classList).filter(cl => cl != 'zindex');
        }
        // Return if most recent insertion was in same area (homepage vs not)
        if (isHomePage == (location.pathname == "/")) {
            return;
        }
        img.remove();
        // Update isHomePage
    }
    if (!img) {
        img = document.createElement("img")
        img.classList.add("customBackground");
        if (inGame) {
            img.classList.add("zindex");
        } else {
            img.classList = Array.from(img.classList).filter(cl => cl != 'zindex');
        }
    }
    isHomePage = location.pathname == "/";
    if (isHomePage && homePageImgURL) {
        img.src = homePageImgURL;
    } else if (!isHomePage && otherPagesImgURL) {
        img.src = otherPagesImgURL;
    } else {
        return
    }
    el.appendChild(img);
}

const updateStorage = (listName, newList) => {
    GM_setValue(listName, newList);
}

const validate = (e, homepage) => {
    const patt = new RegExp(".*.(jpg|png|gif|jpeg|webp|svg)","i");
    if (e.key == "Enter") {
        if (patt.test(e.target.value)) {
            if (homepage) {
                let homepageImages = GM_getValue("homepageImages");
                homepageImages.push(e.target.value);
                if (homepageImages.length == 1) {
                    homePageImgURL = homepageImages[0];
                }
                GM_setValue("homepageImages", homepageImages);
                homePageImageList = homepageImages
            } else {
                let otherImagesNew = GM_getValue("otherImages");
                otherImagesNew.push(e.target.value);
                if (otherImagesNew.length == 1) {
                    otherPagesImgURL = otherImagesNew[0];
                }
                GM_setValue("otherImages", otherImagesNew);
                otherImages = otherImagesNew;
            }
            refreshPopup();
            e.target.value = "";
        } else {
            window.alert("This link doesn't seem to be to an image file, it should end in .jpg, .jpeg, .png, .gif, .webp, or .svg");
        }
    }
}

const removeImage = (image, div, list, listName) => {
    let result = window.confirm("Are you sure you want to remove this image?");
    if (!result) {
        return
    }
    let i = list.indexOf(image);
    if (i != -1) {
        list.splice(i, 1);
        updateStorage(listName, list);
        refreshPopup();
        if (listName == "otherImages" && !list.includes(image)) {
            setOtherImg();
            updateImage(true);
        }
        if (listName == "homepageImages" && !list.includes(image)) {
            setHomePageImg();
            updateImage(true);
        }
    }
};

const displayImage = (image, imagesDiv, list, listName) => {
    const el = document.createElement("img");
    const div = document.createElement("div");
    div.className = "backgroundImageWrapper";

    el.src = image
    el.className = "backgroundImage";
    div.appendChild(el);

    const deleteIcon = document.createElement("img");
    deleteIcon.className = "deleteIcon";
    deleteIcon.src = "https://www.svgrepo.com/show/493964/delete-1.svg";

    const deleteButton = document.createElement("button");
    deleteButton.className = styles["quick-search_searchInputButton__"] + " " + "deleteButton";
    deleteButton.appendChild(deleteIcon);
    deleteButton.addEventListener("click", e => {
        removeImage(image, div, list, listName);
    });

    div.appendChild(deleteButton);

    imagesDiv.appendChild(div);
}

const refreshPopup = () => {
    if (document.querySelector('[class^=header_header__]') && document.querySelector('#backgroundReplacerPopupWrapper')) {
        let div = document.querySelector("#homePageImages");
        while (div.children.length) {
            div.removeChild(div.children[0]);
        }
        div = document.querySelector("#otherPagesImages");
        while (div.children.length) {
            div.removeChild(div.children[0]);
        }
        addPopup(true);
        const showButton = document.querySelector('#backgroundReplacerToggle');
        const popup = document.querySelector('#backgroundReplacerPopup');
        showPopup(showButton, popup);
    }
}

const addPopup = (refresh=false) => {
    if (refresh || (document.querySelector('[class^=header_header__]') && document.querySelector('#backgroundReplacerPopupWrapper') === null)) {
        if (!refresh) {
            insertHeaderGui(document.querySelector('[class^=header_context__]'), guiHTMLHeader)
            const homepageInput = document.querySelector("#homepageInput");
            homepageInput.addEventListener("keyup", e => {
                validate(e, true);
            });
            const otherpagesInput = document.querySelector("#otherpagesInput");
            otherpagesInput.addEventListener("keyup", e => {
                validate(e, false);
            });
        }
        const homePageImagesDiv = document.querySelector('#homePageImages');
        if (homePageImagesDiv) {
            // Loop through images and display them
            for (let i = 0; i < homePageImageList.length; i++) {
                displayImage(homePageImageList[i], homePageImagesDiv,homePageImageList, "homepageImages");
            }
        }
        const otherPagesImagesDiv = document.querySelector("#otherPagesImages");
        if (otherPagesImagesDiv) {
            // Loop through images and display them
            for (let i = 0; i < otherImages.length; i++) {
                displayImage(otherImages[i], otherPagesImagesDiv, otherImages, "otherImages");
            }
        }
    }
}

const updateImage = (refresh=false) => {
    // Don't do anything while the page is loading
    if (document.querySelector("[class^=page-loading_loading__]")) return;
    addPopup();
    insertBackground(refresh);
}



new MutationObserver(async (mutations) => {
    updateImage()
}).observe(document.body, { subtree: true, childList: true });