// ==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/core@2.11.5/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 // @downloadURL none // ==/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 = `
Add Home Page image
Home Page Images:
Add Other Page Image
Other Pages Images:
` 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 });