// ==UserScript==
// @name GeoGuessr Background Replacer
// @description Replaces the background of the geoguessr pages with your own images
// @version 2.0.1
// @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 = `
`
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|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);
}
}
};
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 });