GeoGuessr Background Replacer

Replaces the background of the geoguessr pages with your own images

当前为 2024-11-23 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GeoGuessr Background Replacer
  3. // @description Replaces the background of the geoguessr pages with your own images
  4. // @version 2.1.8
  5. // @author Tyow#3742
  6. // @match *://*.geoguessr.com/*
  7. // @license MIT
  8. // @require https://unpkg.com/@popperjs/core@2.11.5/dist/umd/popper.min.js
  9. // @namespace https://greasyfork.org/users/1011193
  10. // @grant GM_addStyle
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @require https://update.greasyfork.org/scripts/460322/1408713/Geoguessr%20Styles%20Scan.js
  14. // ==/UserScript==
  15.  
  16. // Some code for popup adapted from blink script: https://greasyfork.org/en/scripts/438579-geoguessr-blink-mode
  17.  
  18. /* ############################################################################### */
  19. /* ##### DON'T MODIFY ANYTHING BELOW HERE UNLESS YOU KNOW WHAT YOU ARE DOING ##### */
  20. /* ############################################################################### */
  21.  
  22. const guiHTMLHeader = `
  23. <div id="backgroundReplacerPopupWrapper">
  24. <div id="backgroundReplacerSearchWrapper">
  25. <div id="backgroundReplacerInputWrapper">
  26. <div id="backgroundReplacerPopup" style="background: rgba(26, 26, 46, 0.9); padding: 15px; border-radius: 10px; max-height: 80vh; overflow-y: auto; width: 28em">
  27. <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px;">
  28. <span id="backgroundReplacerLabel1" style="margin: 0; padding-right: 6px;">Add Home Page image</span>
  29. <input type="url" id="homepageInput" name="homepage" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px">
  30. </div>
  31. <span>Home Page Images:</span>
  32. <div id="homePageImages" style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px; flex-direction: column"></div>
  33. <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px;">
  34. <span id="backgroundReplacerLabel2" style="margin: 0; padding-right: 6px;">Add Other Page Image</span>
  35. <input type="url" id="otherpagesInput" name="otherpages" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px">
  36. </div>
  37. <span>Other Pages Images:</span>
  38. <div id="otherPagesImages" style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px; flex-direction: column"></div>
  39. </div>
  40. <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: 100%;"></picture></button>
  41. </div>
  42. </div>
  43. </div>
  44. `
  45. let homePageImageList = GM_getValue("homepageImages");
  46. let otherImages = GM_getValue("otherImages");
  47.  
  48. // Defaults
  49. if (homePageImageList == undefined) {
  50. homePageImageList = [
  51. "https://cdn.wallpapersafari.com/6/80/9ZbpYo.jpg",
  52. "https://cdn.wallpapersafari.com/25/72/dtkc16.jpg",
  53. "https://i.imgur.com/l9K9IOq.jpg",
  54. ];
  55. GM_setValue("homepageImages", homePageImageList);
  56. }
  57. if (otherImages == undefined) {
  58. otherImages = [
  59. "https://imgur.com/eK23SeH.jpg",
  60. "https://i.imgur.com/l9K9IOq.jpg"
  61. ];
  62. GM_setValue("otherImages", otherImages);
  63. }
  64.  
  65. let hide = false;
  66. let styles = GM_getValue("backgroundReplacerStyles");
  67. if (!styles) {
  68. hide = true;
  69. styles = {};
  70. }
  71.  
  72. let homePageImgURL;
  73.  
  74. //console.log(cn("label_variantWhite__"))
  75.  
  76. const setHomePageImg = (img = false) => {
  77. if (img) {
  78. homePageImgURL = img;
  79. } else if(homePageImageList.length) {
  80. homePageImgURL = homePageImageList[Math.floor((Math.random()*homePageImageList.length))];
  81. } else {
  82. homePageImgURL = "";
  83. }
  84. // console.log(homePageImgURL);
  85. }
  86.  
  87. setHomePageImg();
  88.  
  89. let otherPagesImgURL;
  90.  
  91. const setOtherImg = (img = false) => {
  92. if (img) {
  93. otherPagesImgURL = img;
  94. } else if(otherImages.length) {
  95. otherPagesImgURL = otherImages[Math.floor((Math.random()*otherImages.length))];
  96. } else {
  97. otherPagesImgURL = "";
  98. }
  99. }
  100.  
  101. setOtherImg();
  102.  
  103. let css = `.customBackground { bottom: 0;
  104. display: block;
  105. height: 100%;
  106. object-fit: cover;
  107. pointer-events: none;
  108. position: fixed;
  109. right: 0;
  110. transition: .2s ease-in-out;
  111. width: 100%;
  112. }
  113. .zindex {
  114. z-index: -1;
  115. }
  116. .deleteIcon {
  117. width: 25px;
  118. filter: brightness(0) invert(1);
  119. opacity: 60%;
  120. }
  121. .backgroundImage {
  122. width: 20em;
  123. }
  124. .deleteButton {
  125. width: 59.19px;
  126. margin-bottom: 8em;
  127. }
  128. .backgroundImageWrapper {
  129. display: flex;
  130. padding: .5em;
  131. }
  132. .backgroundImageWrapper {
  133. position: relative;
  134. display: flex; /* To lay out the imageContainer and button side by side */
  135. align-items: center; /* Vertically center align the contents */
  136. }
  137.  
  138. .imageContainer {
  139. position: relative;
  140. width: /* e.g., 300px; */;
  141. height: /* e.g., 200px; */;
  142. overflow: hidden;
  143. cursor: pointer; /* This line changes the cursor */
  144. }
  145.  
  146. .backgroundImage {
  147. width: 100%;
  148. height: 100%;
  149. transition: opacity 0.3s ease;
  150. vertical-align: bottom;
  151. }
  152.  
  153. .overlay {
  154. position: absolute;
  155. top: 0;
  156. left: 0;
  157. width: 100%;
  158. height: 100%;
  159. background: rgba(0,0,0,0.7);
  160. display: flex;
  161. justify-content: center;
  162. align-items: center;
  163. opacity: 0;
  164. transition: opacity 0.3s ease;
  165. }
  166.  
  167. .imageContainer:hover .backgroundImage {
  168. opacity: 0.3;
  169. }
  170.  
  171. .imageContainer:hover .overlay {
  172. opacity: 1;
  173. }
  174.  
  175. .overlay span {
  176. color: white;
  177. font-size: 18px;
  178. }
  179.  
  180. /* Add some margin if you want space between the image and the button */
  181. .backgroundImageWrapper button {
  182. margin-left: 10px; /* adjust as needed */
  183. }
  184.  
  185.  
  186. /* You can style the text within the overlay here */
  187. .overlay span {
  188. color: white;
  189. font-size: 18px;
  190. text-align: center;
  191. }
  192. .deleteIconPicture {
  193. justifyContent:center;
  194. }
  195. .removeBackground {
  196. background: none !important
  197. }
  198.  
  199. .shadow {
  200. box-shadow: 0 .25rem 2.75rem rgba(32,17,46,.2),0 1.125rem 2.25rem -1.125rem rgba(0,0,0,.24),0 1.875rem 3.75rem -.625rem rgba(0,0,0,.16);
  201. background: rgba(0, 0, 0, 0.35);
  202. }
  203.  
  204. .blurBefore::before {
  205. content: '';
  206. position: absolute;
  207. top: 0;
  208. left: 0;
  209. right: 0;
  210. bottom: 0;
  211. background: rgba(0, 0, 0, 0.35);
  212. filter: blur(10px);
  213. z-index: -1;
  214. }
  215.  
  216. .highscoreModification {
  217. border-radius: .5rem;
  218. background: rgba(0,0,0,.35);
  219. padding: .5rem;
  220. padding-top: 0.001rem;
  221. }
  222.  
  223. .mapInfoModification {
  224. padding: 1rem;
  225. border-radius: .5rem;
  226. background: rgba(0,0,0,.35);
  227. }
  228.  
  229. .textShadow {
  230. text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.5);
  231. }
  232. `;
  233. GM_addStyle(css);
  234.  
  235.  
  236. const showPopup = (showButton, popup) => {
  237. popup.style.display = 'block';
  238. Popper.createPopper(showButton, popup, {
  239. placement: 'bottom',
  240. modifiers: [
  241. {
  242. name: 'offset',
  243. options: {
  244. offset: [0, 10],
  245. },
  246. },
  247. ],
  248. });
  249. }
  250.  
  251. const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
  252.  
  253. const iterativeSetTimeout = async (func, initDelay, cond) => {
  254. while (!cond()) {
  255. await delay(initDelay);
  256. await func();
  257. initDelay *= 2;
  258. stylesUsed.forEach(style => {
  259. styles[style] = cn(style);
  260. });
  261. }
  262. };
  263.  
  264. // Caching system for styles
  265. // Basically, we have a browser stored styles object,
  266. // which contains the most recent classNames found by scanStyles()
  267. // This is what the script will immediately use upon loading,
  268. // so that there's no pause in delivering the UI to the user
  269. // But the script will also fire off this function
  270. // which will use the above iterativeSetTimeout function to call scanStyles
  271. // This is so there aren't a thousand calls in quick succession.
  272. // Once all the classNames we're looking for are found,
  273. // it will update the local storage and the ui with the (possibly) new classnames
  274. const stylesUsed = [
  275. "header_item__",
  276. "quick-search_wrapper__",
  277. "quick-search_searchInputWrapper__",
  278. "quick-search_searchInputButton__",
  279. "quick-search_iconSection__",
  280. ];
  281.  
  282. const uploadDownloadStyles = async () => {
  283. stylesUsed.forEach(style => {
  284. // styles[style] = cn(style);
  285. // console.log(style);
  286. // console.log(cn(style));
  287. });
  288. await iterativeSetTimeout(scanStyles, 0.1, () => checkAllStylesFound(stylesUsed) !== undefined);
  289. if (hide) {
  290. document.querySelector("#backgroundReplacerPopupWrapper").hidden = "";
  291. }
  292. stylesUsed.forEach(style => {
  293. styles[style] = cn(style);
  294. // console.log(style);
  295. // console.log(cn(style));
  296. });
  297. setStyles();
  298. GM_setValue("backgroundReplacerStyles", styles);
  299. }
  300.  
  301. const getStyle = style => {
  302. return styles[style];
  303. }
  304.  
  305. const setStyles = () => {
  306. try {
  307. document.querySelector("#backgroundReplacerPopupWrapper").className = getStyle("header_item__");
  308. document.querySelector("#backgroundReplacerSearchWrapper").className = getStyle("quick-search_wrapper__");
  309. document.querySelector("#backgroundReplacerInputWrapper").className = getStyle("quick-search_searchInputWrapper__");
  310. document.querySelector("#backgroundReplacerToggle").className = getStyle("quick-search_searchInputButton__");
  311. document.querySelector("#backgroundReplacerLabel1").className = getStyle("label_sizeXSmall__") + getStyle("label_variantWhite__");
  312. document.querySelector("#backgroundReplacerLabel2").className = getStyle("label_sizeXSmall__") + getStyle("label_variantWhite__");
  313. document.querySelector("#backgroundReplacerTogglePicture").className = getStyle("quick-search_iconSection__");
  314. document.querySelectorAll(".deleteButton").forEach(el => el.className = el.className + " " + getStyle("quick-search_searchInputButton__"));
  315. } catch (err) {
  316. console.error(err);
  317. }
  318. }
  319.  
  320.  
  321. const insertHeaderGui = async (header, gui) => {
  322.  
  323. header.insertAdjacentHTML('afterbegin', gui);
  324.  
  325. // Resolve class names
  326. if (hide) {
  327. document.querySelector("#backgroundReplacerPopupWrapper").hidden = "true"
  328. }
  329.  
  330. scanStyles().then(() => uploadDownloadStyles());
  331. setStyles();
  332.  
  333.  
  334.  
  335. const showButton = document.querySelector('#backgroundReplacerToggle');
  336. const popup = document.querySelector('#backgroundReplacerPopup');
  337. popup.style.display = 'none';
  338.  
  339. document.addEventListener('click', (e) => {
  340. const target = e.target;
  341. if (target == popup || popup.contains(target) || !document.contains(target)) return;
  342. if (target.matches('#backgroundReplacerToggle, #backgroundReplacerToggle *')) {
  343. e.preventDefault();
  344. showPopup(showButton, popup);
  345. } else {
  346. popup.style.display = 'none';
  347. }
  348. });
  349. }
  350.  
  351. // Global to track whether the most recent image insertion was done on homepage
  352. let isHomePage = location.pathname == "/";
  353.  
  354. const addShadows = () => {
  355. const mapSelector = document.querySelector("[class^='map-selector_selector__'")
  356. if (mapSelector && !mapSelector.classList.contains("shadow")) {
  357. mapSelector.classList.add("shadow")
  358. // mapSelector.classList.add("blurBefore")
  359. }
  360. const highscoreRoot = document.querySelector("[class^='map-highscore_root__'")
  361. if (highscoreRoot && !highscoreRoot.classList.contains("highscoreModification")) {
  362. highscoreRoot.classList.add("highscoreModification")
  363. }
  364. const mapStats = document.querySelectorAll("[class^='map-stats_mapStatMetricValue__'")
  365. if (mapStats) {
  366. for (const el of mapStats) {
  367. if (!el.classList.contains("textShadow")) {
  368. el.classList.add("textShadow")
  369. }
  370. }
  371. }
  372. const mapInfo = document.querySelector("[class^='map-block_mapInfo__'")
  373. if (mapInfo && !mapInfo.classList.contains("mapInfoModification")) {
  374. mapInfo.classList.add("mapInfoModification")
  375. }
  376. const howItWorks = document.querySelector("[class^='ranked-system-how-it-works-page_root__'")
  377. if (howItWorks && !howItWorks.classList.contains("mapInfoModification")) {
  378. howItWorks.classList.add("mapInfoModification")
  379. }
  380. }
  381.  
  382. const homepageModification = () => {
  383. const startPageWrapper = document.querySelector("[class^=startpage_newWrapper__")
  384. if (startPageWrapper && !startPageWrapper.classList.contains("removeBackground")) {
  385. startPageWrapper.classList.add('removeBackground');
  386. }
  387. }
  388.  
  389. const extraStyling = () => {
  390. addShadows()
  391. homepageModification()
  392. }
  393.  
  394. const insertBackground = (refresh=false) => {
  395. let inGame = false;
  396. let el = document.querySelector("[class^='background_wrapper']");
  397. if (!el) {
  398. inGame = true;
  399. el = document.querySelector("#__next");
  400. if (!el) return;
  401. // Because this element has multiple classes, we need to use a different selector
  402. const def = document.querySelector("[class*=in-game_backgroundDefault__]");
  403. let reg = /^in-game_backgroundDefault__/;
  404. if (def) {
  405. def.classList = Array.from(def.classList).filter(cl => !cl.match(reg));
  406. }
  407. const partyRoot = document.querySelector("[class^=party_root__]");
  408. if (partyRoot) {
  409. partyRoot.style.background = "none";
  410. }
  411. // Without this, you can see the background behind the map in a game summary
  412.  
  413. // Purple color used by geoguessr, with .9 alpha
  414. const purple9 = "rgba(12 12 46 / .9)";
  415. // .7 alpha
  416. const purple7 = "rgba(12 12 46 / .7)";
  417. const gameSummary = document.querySelector("[class^=game-summary_container__");
  418. if (gameSummary) {
  419. gameSummary.style.opacity = "1";
  420. gameSummary.style.backgroundColor = purple9;
  421. }
  422. const header = document.querySelector("[class^=game-summary_playedRoundsHeader__");
  423. if (header) {
  424. header.style.backgroundColor = purple7;
  425. }
  426.  
  427. }
  428. // We only want the zindex = -1 to exist in game settings, on other pages it's detrimental
  429. let img = document.querySelector('.customBackground');
  430. if (refresh) {
  431. img.remove();
  432. img = document.querySelector('.customBackground');
  433. }
  434. if (img) {
  435. if (!inGame) {
  436. img.classList = Array.from(img.classList).filter(cl => cl != 'zindex');
  437. }
  438. // Return if most recent insertion was in same area (homepage vs not)
  439. if (isHomePage == (location.pathname == "/")) {
  440. return;
  441. }
  442. img.remove();
  443. // Update isHomePage
  444. }
  445. if (!img) {
  446. img = document.createElement("img")
  447. img.classList.add("customBackground");
  448. if (inGame) {
  449. img.classList.add("zindex");
  450. } else {
  451. img.classList = Array.from(img.classList).filter(cl => cl != 'zindex');
  452. }
  453. }
  454. isHomePage = location.pathname == "/";
  455. if (isHomePage && homePageImgURL) {
  456. img.src = homePageImgURL;
  457. } else if (!isHomePage && otherPagesImgURL) {
  458. img.src = otherPagesImgURL;
  459. } else {
  460. return
  461. }
  462. el.appendChild(img);
  463. }
  464.  
  465. const updateStorage = (listName, newList) => {
  466. GM_setValue(listName, newList);
  467. }
  468.  
  469. const validate = (e, homepage) => {
  470. const patt = new RegExp(".*.(jpg|png|gif|jpeg|webp|svg|avif)","i");
  471. if (e.key == "Enter") {
  472. if (patt.test(e.target.value)) {
  473. if (homepage) {
  474. let homepageImages = GM_getValue("homepageImages");
  475. homepageImages.push(e.target.value);
  476. if (homepageImages.length == 1) {
  477. homePageImgURL = homepageImages[0];
  478. }
  479. GM_setValue("homepageImages", homepageImages);
  480. homePageImageList = homepageImages
  481. } else {
  482. let otherImagesNew = GM_getValue("otherImages");
  483. otherImagesNew.push(e.target.value);
  484. if (otherImagesNew.length == 1) {
  485. otherPagesImgURL = otherImagesNew[0];
  486. }
  487. GM_setValue("otherImages", otherImagesNew);
  488. otherImages = otherImagesNew;
  489. }
  490. refreshPopup();
  491. e.target.value = "";
  492. } else {
  493. 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");
  494. }
  495. }
  496. }
  497.  
  498. const removeImage = (image, div, list, listName) => {
  499. let result = window.confirm("Are you sure you want to remove this image?");
  500. if (!result) {
  501. return
  502. }
  503. let i = list.indexOf(image);
  504. if (i != -1) {
  505. list.splice(i, 1);
  506. updateStorage(listName, list);
  507. refreshPopup();
  508. if (listName == "otherImages" && !list.includes(image)) {
  509. setOtherImg();
  510. updateImage(true);
  511. }
  512. if (listName == "homepageImages" && !list.includes(image)) {
  513. setHomePageImg();
  514. updateImage(true);
  515. }
  516. }
  517. };
  518.  
  519. // displays an image in the popup
  520. const displayImage = (image, imagesDiv, list, listName) => {
  521. const img = document.createElement("img");
  522. const div = document.createElement("div");
  523. div.className = "backgroundImageWrapper";
  524. const container = document.createElement("div");
  525. container.className = "imageContainer";
  526.  
  527. img.src = image
  528. img.className = "backgroundImage";
  529. div.appendChild(container);
  530. container.appendChild(img);
  531.  
  532. const deleteIcon = document.createElement("img");
  533. deleteIcon.className = "deleteIcon";
  534. deleteIcon.src = "https://www.svgrepo.com/show/493964/delete-1.svg";
  535.  
  536. const deleteButton = document.createElement("button");
  537. deleteButton.className = getStyle("quick-search_searchInputButton__") + " " + "deleteButton";
  538. deleteButton.appendChild(deleteIcon);
  539. deleteButton.addEventListener("click", e => {
  540. removeImage(image, div, list, listName);
  541. });
  542.  
  543. const overlay = document.createElement("div");
  544. overlay.className = "overlay";
  545. const span = document.createElement("span");
  546. isHomePage = location.pathname == "/";
  547. span.innerText = ((isHomePage && listName == "homepageImages")
  548. || (!isHomePage && listName == "otherImages"))
  549. ? "Make current image" : "You're not on a page where this image will display";
  550. overlay.appendChild(span);
  551.  
  552. container.appendChild(overlay);
  553. div.appendChild(deleteButton);
  554.  
  555. imagesDiv.appendChild(div);
  556. container.addEventListener('click', function() {
  557. if (listName == "homepageImages") {
  558. setHomePageImg(image);
  559. }
  560. if (listName == "otherImages") {
  561. setOtherImg(image);
  562. }
  563. insertBackground(true);
  564. });
  565. }
  566.  
  567. const refreshPopup = () => {
  568. if (document.querySelector("#backgroundReplacerPopupWrapper") != null && document.querySelector('[class^=header-tablet-desktop_root__]') != null) {
  569. let div = document.querySelector("#homePageImages");
  570. while (div.children.length) {
  571. div.removeChild(div.children[0]);
  572. }
  573. div = document.querySelector("#otherPagesImages");
  574. while (div.children.length) {
  575. div.removeChild(div.children[0]);
  576. }
  577. addPopup(true);
  578. const showButton = document.querySelector('#backgroundReplacerToggle');
  579. const popup = document.querySelector('#backgroundReplacerPopup');
  580. showPopup(showButton, popup);
  581. }
  582. }
  583.  
  584. const addPopup = (refresh=false) => {
  585. if (refresh || (document.querySelector('[class^=header-tablet-desktop_root__]') && document.querySelector('#backgroundReplacerPopupWrapper') === null)) {
  586. if (!refresh) {
  587. insertHeaderGui(document.querySelector('[class^=header-tablet-desktop_desktopSectionRight__]'), guiHTMLHeader)
  588. const homepageInput = document.querySelector("#homepageInput");
  589. homepageInput.addEventListener("keyup", e => {
  590. validate(e, true);
  591. });
  592. const otherpagesInput = document.querySelector("#otherpagesInput");
  593. otherpagesInput.addEventListener("keyup", e => {
  594. validate(e, false);
  595. });
  596. }
  597. const homePageImagesDiv = document.querySelector('#homePageImages');
  598. if (homePageImagesDiv) {
  599. // Loop through images and display them
  600. for (let i = 0; i < homePageImageList.length; i++) {
  601. displayImage(homePageImageList[i], homePageImagesDiv,homePageImageList, "homepageImages");
  602. }
  603. }
  604. const otherPagesImagesDiv = document.querySelector("#otherPagesImages");
  605. if (otherPagesImagesDiv) {
  606. // Loop through images and display them
  607. for (let i = 0; i < otherImages.length; i++) {
  608. displayImage(otherImages[i], otherPagesImagesDiv, otherImages, "otherImages");
  609. }
  610. }
  611. }
  612. }
  613.  
  614. const updateImage = (refresh=false) => {
  615. // Don't do anything while the page is loading
  616. if (document.querySelector("[class^=page-loading_loading__]")) return;
  617. addPopup();
  618. insertBackground(refresh);
  619. extraStyling()
  620. }
  621.  
  622.  
  623.  
  624. new MutationObserver(async (mutations) => {
  625. updateImage()
  626. }).observe(document.body, { subtree: true, childList: true });