Geoguessr Blink Mode

Shows the round briefly, then screen goes black and you have unlimited time to make your guess.

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

  1. // ==UserScript==
  2. // @name Geoguessr Blink Mode
  3. // @description Shows the round briefly, then screen goes black and you have unlimited time to make your guess.
  4. // @version 1.2.5
  5. // @author macca#8949
  6. // @license MIT
  7. // @match https://www.geoguessr.com/*
  8. // @require https://unpkg.com/@popperjs/core@2.11.5/dist/umd/popper.min.js
  9. // @run-at document-start
  10. // @grant none
  11. // @require https://greasyfork.org/scripts/460322-geoguessr-styles-scan/code/Geoguessr%20Styles%20Scan.js?version=1151654
  12. // @namespace https://greasyfork.org/en/scripts/438579-geoguessr-blink-mode
  13. // @icon https://www.svgrepo.com/show/40039/eye.svg
  14. // ==/UserScript==
  15.  
  16.  
  17. const guiEnabled = true
  18. // ^^^^ Set to false (all lowercase) if you want to hide the GUI and manually enable the script/set the time, otherwise true
  19.  
  20. let timeLimit = 1.5
  21. // ^^^ Modify this number above to change the time
  22.  
  23. let roundDelay = 0
  24. // ^ Modify this number above to change the length of time the round is delayed for
  25.  
  26.  
  27.  
  28. // --------- DON'T MODIFY ANYTHING BELOW THIS LINE -------- //
  29.  
  30.  
  31. const classicGameGuiClasses = ["section_sectionHeader__", "bars_root__", "start-standard-game_settings__", "game-options_optionLabel__"];
  32. const classicGameGuiHTML = () => `
  33. <div class="${cn("section_sectionHeader__")} ${cn("section_sizeMedium__")}"><div class="${cn("bars_root__")} ${cn("bars_center__")}"><div class="${cn("bars_before__")} ${cn("bars_lengthLong__")}"></div><span class="${cn("bars_content__")}"><h3>Blink Mode settings</h3></span><div class="${cn("bars_after__")} ${cn("bars_lengthLong__")}"></div></div></div>
  34. <div class="${cn("start-standard-game_settings__")}">
  35. <div style="display: flex; justify-content: space-around;">
  36. <div style="display: flex; align-items: center;">
  37. <span class="${cn("game-options_optionLabel__")}" style="margin: 0; padding-right: 6px;">Enabled</span>
  38. <input type="checkbox" id="enableScript" onclick="toggleBlinkMode(this)" class="${cn("toggle_toggle__")}">
  39. </div>
  40.  
  41. <div style="display: flex; align-items: center;">
  42. <span class="${cn("game-options_optionLabel__")}" style="margin: 0; padding-right: 6px;">Time (Seconds)</span>
  43. <input type="text" id="blinkTime" onchange="changeBlinkTime(this)" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 60px;">
  44. </div>
  45. </div>
  46. <div style="margin-top: 10px">
  47. <span class="${cn("game-options_optionLabel__")}" style="margin: 0; padding-right: 6px;">Round Delay (Seconds)</span>
  48. <input type="text" id="delayTime" onchange="changeDelayTime(this)" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 60px;">
  49. </div>
  50. </div>
  51. `
  52.  
  53. const friendLobbyGuiClasses = ["section_sectionHeader__", "bars_root__", "game-options_optionLabel__"];
  54. const friendLobbyGuiHTML = () => `
  55. <div class="${cn("section_sectionHeader__")} ${cn("section_sizeMedium__")}" style="margin-top: 10px"><div class="${cn("bars_root__")}"><span class="${cn("bars_content__")}"><h2>Blink Mode Settings</h2></span><div class="${cn("bars_after__")} ${cn("bars_lengthLong__")}"></div></div></div>
  56. <div class="${cn("start-standard-game_settings__")}" style="margin-top: 8px">
  57. <div style="display: flex; justify-content: space-around;">
  58. <div style="display: flex; align-items: center;">
  59. <span class="${cn("game-options_optionLabel__")}" style="margin: 0; padding-right: 6px;">Enabled</span>
  60. <input type="checkbox" id="enableScript" onclick="toggleBlinkMode(this)" class="${cn("toggle_toggle__")}">
  61. </div>
  62.  
  63. <div style="display: flex; align-items: center;">
  64. <span class="${cn("game-options_optionLabel__")}" style="margin: 0; padding-right: 6px;">Time (Seconds)</span>
  65. <input type="text" id="blinkTime" onchange="changeBlinkTime(this)" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 60px;">
  66. </div>
  67.  
  68. <div style="display: flex; align-items: center;">
  69. <span class="${cn("game-options_optionLabel__")}" style="margin: 0; padding-right: 6px;">Round Delay (Seconds)</span>
  70. <input type="text" id="delayTime" onchange="changeDelayTime(this)" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 60px;">
  71. </div>
  72. </div>
  73. </div>
  74. `
  75.  
  76. const guiHeaderClasses = ["header_item__", "quick-search_wrapper__", "slanted-wrapper_root__", "label_sizeXSmall__", "toggle_toggle__"];
  77. const guiHTMLHeader = () => `
  78. <div id="blinkHeaderToggle" class="${cn("header_item__")}">
  79. <div class="${cn("quick-search_wrapper__")}">
  80. <div class="${cn("slanted-wrapper_root__")} ${cn("slanted-wrapper_variantGrayTransparent__")}">
  81. <div class="${cn("slanted-wrapper_start__")} ${cn("slanted-wrapper_right__")}"></div>
  82. <div class="${cn("quick-search_searchInputWrapper__")}">
  83. <div id="popup" style="background: rgba(26, 26, 46, 0.9); padding: 15px; width: 200px; border-radius: 10px;">
  84. <div style="display: flex; justify-content: space-between; align-items: center;">
  85. <span class="${cn("label_sizeXSmall__")}">Enabled</span>
  86. <input type="checkbox" id="enableScriptHeader" class="${cn("toggle_toggle__")}" onclick="toggleBlinkMode(this)">
  87. </div>
  88.  
  89. <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px;">
  90. <span class="${cn("label_sizeXSmall__")}">Time (Seconds)</span>
  91. <input type="text" id="blinkTimeHeader" onchange="changeBlinkTime(this)" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 60px;">
  92. </div>
  93.  
  94. <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px;">
  95. <span class="${cn("label_sizeXSmall__")}" style="margin: 0; padding-right: 6px;">Round Delay (Seconds)</span>
  96. <input type="text" id="delayTimeHeader" onchange="changeDelayTime(this)" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 60px;">
  97. </div>
  98. </div>
  99. <button style="width: 59.19px" id="headerGuiToggle" class="${cn("quick-search_searchInputButton__")}"><picture style="justify-content: center" class="${cn("quick-search_iconSection__")}"><img src="https://www.svgrepo.com/show/40039/eye.svg" style="width: 15px; filter: brightness(0) invert(1); opacity: 60%;"></picture></button>
  100. </div>
  101. <div class="${cn("slanted-wrapper_end__")} ${cn("slanted-wrapper_right__")}"></div>
  102. </div>
  103. </div>
  104. </div>
  105. `
  106.  
  107. const guiPartyHeaderClasses = ["header_item__", "slanted-wrapper_root__", "game-options_optionLabel__"];
  108. const guiPartyHeader = () => `
  109. <div id="blinkHeaderToggle" class="${cn("header_item__")}" style="margin-right: 1rem;">
  110. <div id="popup" style="background: rgba(26, 26, 46, 0.9); padding: 15px; width: 200px; border-radius: 10px; z-index: 999;">
  111. <div style="display: flex; justify-content: space-between; align-items: center;">
  112. <span class="${cn("game-options_optionLabel__")}">Enabled</span>
  113. <input type="checkbox" id="enableScriptHeader" class="${cn("toggle_toggle__")}" onclick="toggleBlinkMode(this)">
  114. </div>
  115.  
  116. <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px;">
  117. <span class="${cn("game-options_optionLabel__")}">Time (Seconds)</span>
  118. <input type="text" id="blinkTimeHeader" onchange="changeBlinkTime(this)" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 60px;">
  119. </div>
  120.  
  121. <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px;">
  122. <span class="${cn("game-options_optionLabel__")}" style="margin: 0; padding-right: 6px;">Round Delay (Seconds)</span>
  123. <input type="text" id="delayTimeHeader" onchange="changeDelayTime(this)" style="background: rgba(255,255,255,0.1); color: white; border: none; border-radius: 5px; width: 60px;">
  124. </div>
  125. </div>
  126. <div class="${cn("quick-search_wrapper__")}">
  127. <div class="${cn("slanted-wrapper_root__")} ${cn("slanted-wrapper_variantGrayTransparent__")}">
  128. <div class="${cn("slanted-wrapper_start__")} ${cn("slanted-wrapper_right__")}"></div>
  129. <div>
  130. <button id="headerGuiToggle" style="width: 59.19px; background-color: inherit;border: initial;cursor: pointer;min-height: 2rem;min-width: 2rem;padding: var(--padding-y) var(--padding-x);"><picture style="justify-content: center" class="${cn("quick-search_iconSection__")}"><img src="https://www.svgrepo.com/show/40039/eye.svg" style="width: 15px; filter: brightness(0) invert(1); opacity: 60%;"></picture></button>
  131. </div>
  132. <div class="${cn("slanted-wrapper_end__")} ${cn("slanted-wrapper_right__")}"></div>
  133. </div>
  134. </div>
  135. </div>
  136. `
  137.  
  138. if (localStorage.getItem('blinkEnabled') == null) {
  139. localStorage.setItem('blinkEnabled', 'disabled');
  140. }
  141.  
  142. if (!guiEnabled) {
  143. localStorage.setItem('blinkEnabled', 'enabled');
  144. }
  145.  
  146. if (localStorage.getItem('blinkTime') == null || isNaN(localStorage.getItem('blinkTime'))) {
  147. localStorage.setItem('blinkTime', timeLimit);
  148. }
  149. if (localStorage.getItem('delayTime') == null || isNaN(localStorage.getItem('delayTime'))) {
  150. localStorage.setItem('delayTime', roundDelay);
  151. }
  152.  
  153. if (guiEnabled) {
  154. timeLimit = parseFloat(localStorage.getItem('blinkTime'));
  155. roundDelay = parseFloat(localStorage.getItem('delayTime'));
  156. }
  157.  
  158. window.toggleBlinkMode = (e) => {
  159. localStorage.setItem('blinkEnabled', e.checked ? 'enabled' : 'disabled');
  160. if (!e.checked) {
  161. try { showPanoramaCached(); } catch {}
  162. }
  163.  
  164. if (document.querySelector('#enableScript')) {
  165. document.querySelector('#enableScript').checked = e.checked;
  166. }
  167. if (document.querySelector('#enableScriptHeader')) {
  168. document.querySelector('#enableScriptHeader').checked = e.checked;
  169. }
  170. }
  171.  
  172. window.changeBlinkTime = (e) => {
  173. if (!isNaN(e.value)) {
  174. localStorage.setItem('blinkTime', parseFloat(e.value));
  175. timeLimit = parseFloat(e.value);
  176.  
  177. if (document.querySelector('#blinkTime')) {
  178. document.querySelector('#blinkTime').value = e.value;
  179. }
  180. if (document.querySelector('#blinkTimeHeader')) {
  181. document.querySelector('#blinkTimeHeader').value = e.value;
  182. }
  183. }
  184. }
  185.  
  186. window.changeDelayTime = (e) => {
  187. if (!isNaN(e.value)) {
  188. localStorage.setItem('delayTime', parseFloat(e.value));
  189. roundDelay = parseFloat(e.value);
  190.  
  191. if (document.querySelector('#delayTime')) {
  192. document.querySelector('#delayTime').value = e.value;
  193. }
  194. if (document.querySelector('#delayTimeHeader')) {
  195. document.querySelector('#delayTimeHeader').value = e.value;
  196. }
  197. }
  198. }
  199.  
  200. const insertHeaderGui = (header, gui) => {
  201. header.insertAdjacentHTML('afterbegin', gui);
  202. const showButton = document.querySelector('#headerGuiToggle');
  203. const popup = document.querySelector('#popup');
  204. popup.style.display = 'none';
  205.  
  206. document.addEventListener('click', (e) => {
  207. const target = e.target;
  208. if (target == popup || popup.contains(target)) return;
  209. if (target.matches('#headerGuiToggle, #headerGuiToggle *')) {
  210. e.preventDefault();
  211.  
  212. popup.style.display = 'block';
  213. Popper.createPopper(showButton, popup, {
  214. placement: 'bottom',
  215. modifiers: [
  216. {
  217. name: 'offset',
  218. options: {
  219. offset: [0, 10],
  220. },
  221. },
  222. ],
  223. });
  224. } else {
  225. popup.style.display = 'none';
  226. }
  227.  
  228. if (document.querySelector('#enableScriptHeader')) {
  229. if (localStorage.getItem('blinkEnabled') === 'enabled') {
  230. document.querySelector('#enableScriptHeader').checked = true;
  231. }
  232. document.querySelector('#blinkTimeHeader').value = timeLimit;
  233. document.querySelector('#delayTimeHeader').value = roundDelay;
  234. }
  235. });
  236. }
  237.  
  238. const checkInsertGui = () => {
  239. // Play page for classic games
  240. if (document.querySelector('[class*=radio-box_root__]') && document.querySelector('#enableScript') === null && checkAllStylesFound(classicGameGuiClasses)) {
  241. document.querySelector('[class*=section_sectionMedium__]').insertAdjacentHTML('beforeend', classicGameGuiHTML());
  242. if (localStorage.getItem('blinkEnabled') === 'enabled') {
  243. document.querySelector('#enableScript').checked = true;
  244. }
  245. document.querySelector('#blinkTime').value = timeLimit;
  246. document.querySelector('#delayTime').value = roundDelay;
  247. }
  248.  
  249. // Lobby for friends party games
  250. if (document.querySelector('[class*=game-options_root__]') && document.querySelector('#enableScript') === null && checkAllStylesFound(friendLobbyGuiClasses)) {
  251. document.querySelector('[class*=game-options_optionGroup__]').insertAdjacentHTML('beforeend', friendLobbyGuiHTML());
  252. if (localStorage.getItem('blinkEnabled') === 'enabled') {
  253. document.querySelector('#enableScript').checked = true;
  254. }
  255. document.querySelector('#blinkTime').value = timeLimit;
  256. document.querySelector('#delayTime').value = roundDelay;
  257. }
  258.  
  259. // Header
  260. if (document.querySelector('[class*=header_header__]') && document.querySelector('#blinkHeaderToggle') === null && checkAllStylesFound(guiHeaderClasses)) {
  261. insertHeaderGui(document.querySelector('[class*=header_context__]'), guiHTMLHeader())
  262. } else if (document.querySelector('[class*=party-header_root__]') && document.querySelector('#blinkHeaderToggle') === null && checkAllStylesFound(guiPartyHeaderClasses)) {
  263. insertHeaderGui(document.querySelector('[class*=party-header_right__]'), guiPartyHeader())
  264. }
  265. }
  266.  
  267. let mapRoot = null;
  268. function hidePanorama() {
  269. mapRoot = document.querySelector('.mapsConsumerUiSceneInternalCoreScene__root') || mapRoot;
  270. hidePanoramaCached();
  271. }
  272.  
  273. function hidePanoramaCached() {
  274. mapRoot.style.filter = 'brightness(0%)';
  275. }
  276.  
  277. function showPanorama() {
  278. mapRoot = document.querySelector('.mapsConsumerUiSceneInternalCoreScene__root') || mapRoot;
  279. showPanoramaCached();
  280. }
  281.  
  282. function showPanoramaCached() {
  283. mapRoot.style.filter = 'brightness(100%)';
  284. }
  285.  
  286. function isLoading() {
  287. return document.querySelector('[class*=fullscreen-spinner_root__]') || !document.querySelector('.widget-scene-canvas');
  288. }
  289.  
  290. let wasBackdropThereOrLoading = false;
  291. function isBackdropThereOrLoading() {
  292. return isLoading() // loading
  293. || document.querySelector('[class*=result-layout_root__]') // classic
  294. || document.querySelector('[class*=overlay_backdrop__]') // duels / team duels
  295. || document.querySelector('[class*=game_backdrop__]') || document.querySelector('[class*=overlays_backdrop__]') // live challenges
  296. || document.querySelector('[class*=popup_backdrop__]') // BR
  297. || document.querySelector('[class*=game-starting_container__]') || document.querySelector('[class*=round-score_container__]') // bullseye
  298. || document.querySelector('[class*=overlay-modal_backlight__]'); // city streaks
  299. }
  300.  
  301. let showTimeoutID = null
  302. let hideTimeoutID = null
  303. function triggerBlink() {
  304. hidePanorama();
  305. clearTimeout(showTimeoutID);
  306. showTimeoutID = setTimeout(showPanorama, roundDelay * 1000);
  307. clearTimeout(hideTimeoutID);
  308. hideTimeoutID = setTimeout(hidePanorama, (timeLimit + roundDelay) * 1000);
  309. }
  310.  
  311. let observer = new MutationObserver((mutations) => {
  312. if (guiEnabled) {
  313. scanStyles().then(_ => { checkInsertGui(); });
  314. }
  315.  
  316. if (localStorage.getItem('blinkEnabled') === 'enabled') {
  317. if (isBackdropThereOrLoading()) {
  318. wasBackdropThereOrLoading = true;
  319. if (!isLoading()) hidePanorama();
  320. } else if (wasBackdropThereOrLoading) {
  321. wasBackdropThereOrLoading = false;
  322. triggerBlink();
  323. }
  324. }
  325.  
  326. });
  327.  
  328. observer.observe(document.body, {
  329. characterDataOldValue: false,
  330. subtree: true,
  331. childList: true,
  332. characterData: false
  333. });