Greasy Fork

来自缓存

Greasy Fork is available in English.

GeoGuessr Background Replacer

Replaces the background of the geoguessr pages with your own images

当前为 2023-07-30 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// Some code for popup adapted from blink script: http://greasyfork.icu/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, initDelay, cond) => {
    while (!cond()) {
        await delay(initDelay);
        await func();
        initDelay *= 2;
        console.log(Date.now());
    }
};

// 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 stylesUsed = [
    "header_item__",
    "quick-search_wrapper__",
    "slanted-wrapper_root__",
    "slanted-wrapper_variantGrayTransparent__",
    "slanted-wrapper_start__",
    "quick-search_searchInputWrapper__",
    "slanted-wrapper_end__",
    "slanted-wrapper_right__",
    "quick-search_searchInputButton__",
    "quick-search_iconSection__",
    "label_sizeXSmall__",
    "label_variantWhite__"
];

const uploadDownloadStyles = async () => {
    await iterativeSetTimeout(scanStyles, 0.1, () => checkAllStylesFound(stylesUsed) !== undefined);
    if (hide) {
        document.querySelector("#backgroundReplacerPopupWrapper").hidden = "";
    }
    stylesUsed.forEach(style => {
        styles[style] = cn(style);
    });
    setStyles();
    GM_setValue("backgroundReplacerStyles", styles);
}

const getStyle = style => {
    return styles[style];
}

const setStyles = () => {
    try {
        document.querySelector("#backgroundReplacerPopupWrapper").className = getStyle("header_item__");
        document.querySelector("#backgroundReplacerSearchWrapper").className = getStyle("quick-search_wrapper__");
        document.querySelector("#backgroundReplacerSlantedRoot").className = getStyle("slanted-wrapper_root__") + " " + getStyle("slanted-wrapper_variantGrayTransparent__");
        document.querySelector("#backgroundReplacerSlantedStart").className = getStyle("slanted-wrapper_start__")+ " " + getStyle("slanted-wrapper_right__");
        document.querySelector("#backgroundReplacerInputWrapper").className = getStyle("quick-search_searchInputWrapper__");
        document.querySelector("#backgroundReplacerSlantedEnd").className = getStyle("slanted-wrapper_end__")+ " " + getStyle("slanted-wrapper_right__");
        document.querySelector("#backgroundReplacerToggle").className = getStyle("quick-search_searchInputButton__");
        document.querySelector("#backgroundReplacerLabel1").className = getStyle("label_sizeXSmall__") + getStyle("label_variantWhite__");
        document.querySelector("#backgroundReplacerLabel2").className = getStyle("label_sizeXSmall__") + getStyle("label_variantWhite__");
        document.querySelector("#backgroundReplacerTogglePicture").className = getStyle("quick-search_iconSection__");
        document.querySelectorAll(".deleteButton").forEach(el => el.className = el.className + " " + getStyle("quick-search_searchInputButton__"));
    } catch (err) {
        console.error(err);
    }
}


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|avif)","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, .avif, 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);
        }
    }
};

// displays an image in the popup
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 = getStyle("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 });