A better search

Completely strips the original search engine and replaces it with a more fleshed out version where you can use filters and see more info.

  1. // ==UserScript==
  2. // @name A better search
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2.4
  5. // @description Completely strips the original search engine and replaces it with a more fleshed out version where you can use filters and see more info.
  6. // @author Lemson
  7. // @match https://www.geoguessr.com/search
  8. // @match https://www.geoguessr.com/
  9. // @icon https://www.clipartmax.com/png/full/15-150759_search-icon-search-icon-png-blue.png
  10. // @grant GM_addStyle
  11. // @require https://gf.qytechs.cn/scripts/460322-geoguessr-styles-scan/code/Geoguessr%20Styles%20Scan.js?version=1151668
  12. // @license MIT
  13. // @run-at document-idle
  14. // ==/UserScript==
  15. // Removes the old search, and keeps it gone, hopefully this doesnt fuck anything else up :D
  16. const observer = new MutationObserver(() => {
  17. setTimeout(() => {
  18. document.querySelectorAll('[class*="search_center__"]').forEach((element) => {
  19. if (element.parentNode) element.parentNode.remove();
  20. });
  21. }, 200);
  22. });
  23. observer.observe(document.documentElement, { childList: true, subtree: true });
  24.  
  25. if (window.location.href === "https://www.geoguessr.com/") {
  26. function newStartPageCSS() {
  27. const inputCSS = `
  28. .quicksearch-input{
  29. background-color: rgba(0,0,0,0);
  30. border: none;
  31. padding-left: 1.3rem;
  32. color: white;
  33. }
  34. `;
  35. GM_addStyle(inputCSS);
  36.  
  37. const searchButtonCSS = `
  38. .slanted-button-container{
  39. display: inline-block;
  40. scale: .95;
  41. transition: .2s;
  42. }
  43. .slanted-button-container:hover{
  44. transition: .2s;
  45. }
  46. .slanted-wrapper-root{
  47. position: relative;
  48. z-index: 0;
  49. }
  50. .slanted-wrapper_variantGrayTransparent{
  51. }
  52. .slanted-wrapper-start{
  53. left: 0;
  54. }
  55. .slanted-wrapper-right{
  56. bottom: 0;
  57. overflow: hidden;
  58. position: absolute;
  59. top: 0;
  60. width: 50%;
  61. z-index: -1;
  62. }
  63. .slanted-wrapper-right:before{
  64. transform-origin: bottom;
  65. border-radius: 0.25rem 0 0 0.25rem;
  66. transform: skewX(-12deg);
  67. left: 0;
  68. padding-right: .0625rem;
  69. width: 100%;
  70. background: var(--ds-color-black-40);
  71. bottom: 0;
  72. content: "";
  73. position: absolute;
  74. top: 0;
  75. z-index: -1;
  76. }
  77. .slanted-button_root{
  78. --skew-angle: -10deg;
  79. --content-skew-angle: 0;
  80. --variant-background-color: var(--ds-color-black-20);
  81. --border-radius: 0.25rem;
  82. --content-color: var(--ds-color-white);
  83. --content-padding: 0.6rem 1rem;
  84. }
  85. .slanted-button_button{
  86. background: none;
  87. border: initial;
  88. cursor: pointer;
  89. margin: 0;
  90. min-height: 3rem;
  91. padding: 0;
  92. display: flex;
  93. flex-direction: row-reverse;
  94. align-items: center;
  95. }
  96. .slanted-button_content{
  97. color: var(--content-color);
  98. padding: var(--content-padding);
  99. }
  100. .slanted-button_contentSizeLarge{
  101. --content-padding: 0.6rem 1rem;
  102. }
  103. .search-button-root{
  104. background-color: transparent;
  105. border: unset;
  106. cursor: pointer;
  107. display: flex;
  108. flex: 0 0 3rem;
  109. justify-content: center;
  110. position: relative;
  111. z-index: 1;
  112. }
  113. .slanted-wrapper-end{
  114. left: 50%;
  115. }
  116. .slanted-wrapper_right{
  117. bottom: 0;
  118. overflow: hidden;
  119. position: absolute;
  120. top: 0;
  121. width: 50%;
  122. z-index: -1;
  123. }
  124. .slanted-wrapper_right:before{
  125. transform-origin: top;
  126. border-radius: 0 0.25rem 0.25rem 0;
  127. transform: skewX(-12deg);
  128. padding-left: .0625rem;
  129. right: 0;
  130. width: 100%;
  131. background: var(--ds-color-black-40);
  132. bottom: 0;
  133. content: "";
  134. position: absolute;
  135. top: 0;
  136. z-index: -1;
  137. }
  138. `;
  139. GM_addStyle(searchButtonCSS);
  140. }
  141.  
  142. const createNewSearchButton = () => {
  143. const baseHTML = `
  144. <div class="slanted-button-container">
  145. <div class="slanted-wrapper-root slanted-wrapper_variantGrayTransparent">
  146. <div class="slanted-wrapper-start slanted-wrapper-right"></div>
  147. <button class="slanted-button_root slanted-button_button">
  148. <div class="slanted-button_content slanted-button_contentSizeLarge">
  149. <img src="https://svgur.com/i/142d.svg" alt="Search Icon">
  150. </div>
  151. </button>
  152. <div class="slanted-wrapper-end slanted-wrapper_right"></div>
  153. </div>
  154. </div>
  155. `;
  156.  
  157. const header = document.querySelector('div[class^="header_context__"]');
  158. const diver = document.createElement("div");
  159. diver.innerHTML = baseHTML;
  160. header.insertBefore(diver, document.querySelector(".slanted-button_container__6JmyZ"));
  161. return diver;
  162. };
  163.  
  164. const openSearch = () => {
  165. if (!searchOpen) {
  166. const input = document.createElement("div");
  167. input.innerHTML = `
  168. <input placeholder="Search for maps..." type="text" class="quicksearch-input">
  169. `;
  170. document.querySelector(".slanted-button_root").append(input);
  171. searchOpen = true;
  172. }
  173. };
  174.  
  175. const createEventListeners = () => {
  176. searchButton.addEventListener("click", openSearch);
  177.  
  178. document.addEventListener("keydown", function (event) {
  179. if (event.key === "Enter" && document.activeElement == document.querySelector(".quicksearch-input")) {
  180. const input = document.querySelector(".quicksearch-input");
  181. localStorage.setItem("searchTerm", input.value);
  182. window.location.href = "https://www.geoguessr.com/search";
  183. }
  184. });
  185. };
  186.  
  187. let searchOpen = false;
  188.  
  189. const searchButton = createNewSearchButton();
  190. newStartPageCSS();
  191. createEventListeners();
  192. }
  193.  
  194. //Search page \/
  195. if (window.location.href === "https://www.geoguessr.com/search") {
  196. function newSearchPageCSS() {
  197. const CSS = `
  198. .main-search-div{
  199. height: 100%;
  200. }
  201. .search-page-main{
  202. width: 100vw;
  203. height: 100%;
  204. display: flex;
  205. align-items: center;
  206. justify-content: center;
  207. flex-direction: column;
  208. }
  209. .input-main-container{
  210. width: 100vw;
  211. height: 100%;
  212. display: flex;
  213. justify-content: center;
  214. align-items: center;
  215. }
  216. .search-container{
  217. width: 40%;
  218. display: flex;
  219. justify-content: center;
  220. align-items: center;
  221. }
  222. .search-input{
  223. width: 100%;
  224. background-color: rgb(255 255 255 / 5%);
  225. border: 1px solid black;
  226. border-radius: 10rem;
  227. color: white;
  228. font-size: 1.2rem;
  229. padding-left: 1.5rem;
  230. }
  231. .search-results{
  232. }
  233. .search-item{
  234. display: flex;
  235. flex-direction: row;
  236. justify-content: center;
  237. gap: 1rem;
  238. margin-top: 2rem;
  239. padding-bottom: 1rem;
  240. border-bottom: 1px solid rgba(0,0,0,0.5);
  241. transition: .2s;
  242. }
  243. .search-item:hover{
  244. transition: .2s;
  245. scale: 1.01;
  246. }
  247. .author-map-name{
  248. width: 20rem;
  249. }
  250. .map-name{
  251. font-size:1.5rem;
  252. }
  253. .map-avatar{
  254. width: 4rem;
  255. border-radius: .5rem;
  256. }
  257. .stat-view{
  258. width: 5rem;
  259. margin-right: 4rem;
  260. display:flex;
  261. align-items: center;
  262. justify-content: center;
  263. flex-direction: column;
  264. gap: .2rem;
  265. }
  266.  
  267. .dropdown{
  268. width: 40%;
  269. display: flex;
  270. justify-content: center;
  271. align-items: center;
  272. flex-direction: column;
  273. }
  274. .filter-btn {
  275. color: white;
  276. font-size: 1rem;
  277. border: none;
  278. cursor: pointer;
  279. }
  280. .filter-window{
  281. width: 100%;
  282. display: flex;
  283. justify-content: space-around;
  284. }
  285.  
  286. .filter-category-container{
  287. display: flex;
  288. flex-direction: column;
  289. align-items: center;
  290. margin-top: 1rem;
  291. margin-bottom: .5rem;
  292. }
  293. .min-input{
  294. background-color: rgba(0,0,0,0.1);
  295. border: 1px solid #6b6b6b;
  296. border-radius: .7rem;
  297. color: white;
  298. width: 8rem;
  299. margin-top: .1rem;
  300. }
  301. .hide{
  302. display: none;
  303. }
  304.  
  305. .official-toggle-buttons>*{
  306. color: white;
  307. border: 1px solid white;
  308. padding: 1rem;
  309. padding-top: .4rem;
  310. padding-bottom: .4rem;
  311. }
  312. .selectionmode-toggle-buttons>*{
  313. color: white;
  314. border: 1px solid white;
  315. padding: 1rem;
  316. padding-top: .4rem;
  317. padding-bottom: .4rem;
  318. }
  319. .active-button{
  320. background-color: #563b9a;
  321. }
  322. .apply-button{
  323. width: 5rem;
  324. height: 2.5rem;
  325. background-color: transparent;
  326. margin-top: 10%;
  327. color: white;
  328. border: 1px solid white;
  329. border-radius: 2rem;
  330. margin-left: 35%;
  331. }
  332. .apply-button:active{
  333. transition: .05s;
  334. background-color: rgba(255,255,255,0.2);
  335. }
  336. `;
  337. GM_addStyle(CSS);
  338. }
  339. const createNewSearchbar = () => {
  340. const searchHTML = `
  341. <div class="search-page-main">
  342. <div class="search-container">
  343. <input class="search-input" type="text" placeholder="Search for maps or players...">
  344. </div>
  345.  
  346. <div class="dropdown">
  347. <button class="filter-btn">Filters</button>
  348.  
  349. <div class="filter-window">
  350. <div>
  351. <div class="filter-category-container">
  352. <p>Minimum likes</p>
  353. <input id="min-likes" class="min-input" type="number" value=${localStorage.getItem("minLikes")}>
  354. </div>
  355. <div class="filter-category-container">
  356. <p>Minimum locations</p>
  357. <input id="min-locs" class="min-input" type="number" value=${localStorage.getItem("minLocs")}>
  358. </div>
  359. <div class="filter-category-container">
  360. <p>Minimum games played</p>
  361. <input id="min-games-played" class="min-input" type="number" value=${localStorage.getItem(
  362. "minGamesPlayed"
  363. )}>
  364. </div>
  365. <div class="filter-category-container">
  366. <p>Minimum average score</p>
  367. <input id="min-avg-score" class="min-input" type="number" value=${localStorage.getItem("minAvgScore")}>
  368. </div>
  369.  
  370. </div>
  371. <div>
  372. <div class="filter-category-container">
  373. <p>Official</p>
  374. <div class="official-toggle-buttons">
  375. <button id="official">Yes</button>
  376. <button id="both" class="active-button">All</button>
  377. <button id="unofficial">No</button>
  378. </div>
  379. </div>
  380. <div class="filter-category-container">
  381. <p>Selection mode</p>
  382. <div class="selectionmode-toggle-buttons">
  383. <button id="handpicked">Handpicked</button>
  384. <button id="both" class="active-button">All</button>
  385. <button id="polygonal">Polygonal</button>
  386. </div>
  387. </div>
  388.  
  389. <button class="apply-button">Apply</button>
  390.  
  391.  
  392. </div>
  393. </div>
  394. </div>
  395. </div>
  396. `;
  397. const mainDiv = document.querySelector("main");
  398. const dave = document.createElement("div");
  399. dave.classList.add("main-search-div");
  400. dave.innerHTML = searchHTML;
  401. mainDiv.append(dave);
  402. };
  403.  
  404. const createResultsFromSearch = async () => {
  405. const results = await getResults(localStorage.getItem("searchTerm"));
  406. const existingResultsContainer = document.querySelector(".search-results");
  407. if (existingResultsContainer) {
  408. existingResultsContainer.remove();
  409. }
  410. const div = document.createElement("div");
  411. div.innerHTML = "";
  412. div.classList.add("search-results");
  413. document.querySelector(".search-page-main").append(div);
  414. console.log(results);
  415. results.forEach((a) => {
  416. const html = `
  417. <img class="map-avatar" src="https://avatar.map-making.app/${a.id}">
  418. <div class="author-map-name">
  419. <a href="/maps/${a.id}" target="_" class="map-name">${a.name}</a>
  420. <p class="creator-name">Created by: <a href="/user/${a.creatorId}">${a.creator}</a></p>
  421. </div>
  422. <div class="stat-view likes">
  423. <img style="width: 1.5rem;" src="_next/static/media/like-32.1321332a.svg" title="Likes">
  424. ${a.likes}
  425. </div>
  426. <div class="stat-view locs">
  427. <img style="width: 1.5rem;" src="_next/static/media/location-32.73fdcf3f.svg" title="Number of locations">
  428. ${a.coordinateCount}
  429. </div>
  430. <div class="stat-view games">
  431. <img style="width: 1.5rem;" src="_next/static/media/people-32.6e1cc43b.svg" title="Games played">
  432. ${a.numberOfGamesPlayed}
  433. </div>
  434. <div class="stat-view avgScore">
  435. <img style="width: 2rem;" src="https://i.imgur.com/uRdcYBM.png" title="Average score">
  436. ${a.averageScore}
  437. </div>
  438. <div class="stat-view howitwascreated">
  439. ${a.locationSelectionMode === 1 ? "Handpicked" : a.locationSelectionMode === 2 ? "Polygonal" : "Official"}
  440. </div>
  441. `;
  442. const resultContainer = document.createElement("div");
  443. resultContainer.classList.add("search-item");
  444. resultContainer.innerHTML = html;
  445. div.append(resultContainer);
  446. });
  447. };
  448.  
  449. async function getResults(word) {
  450. let mapSearch = await fetch(`https://www.geoguessr.com/api/v3/search/map?page=0&count=50&q=${word}`);
  451. if (!mapSearch) {
  452. console.log("bad response");
  453. }
  454. let data = await mapSearch.json();
  455.  
  456. let moreData = await getAdditionalData(data);
  457. const combinedData = [];
  458. const minLength = Math.min(data.length, moreData.length);
  459. for (let i = 0; i < minLength; i++) {
  460. combinedData.push({ ...moreData[i], ...data[i] });
  461. }
  462.  
  463. let filteredData = await applyFilters(combinedData);
  464.  
  465. return filteredData;
  466. }
  467.  
  468. const getAdditionalData = async (data) => {
  469. const promises = data.map((map) => fetch(`https://www.geoguessr.com/api/maps/${map.id}`));
  470. const responses = await Promise.all(promises);
  471. const extraMapData = await Promise.all(
  472. responses.map(async (resp) => {
  473. try {
  474. return await resp.json();
  475. } catch (error) {
  476. console.error(
  477. "Something went wrong when looking at the map data:",
  478. error,
  479. "(You can most likely ignore this message)"
  480. );
  481. return null;
  482. }
  483. })
  484. );
  485.  
  486. return extraMapData.filter((data) => data !== null);
  487. };
  488.  
  489. const openFilters = () => {
  490. console.log("open filters");
  491. };
  492.  
  493. function applyFilters(data) {
  494. let filteredData = data;
  495. const minLikes = document.getElementById("min-likes").value;
  496. const minLocs = document.getElementById("min-locs").value;
  497. const minGamesPlayed = document.getElementById("min-games-played").value;
  498. const minAvgScore = document.getElementById("min-avg-score").value;
  499.  
  500. //Filter min likes
  501. filteredData = filteredData.filter((item) => {
  502. return item.likes >= minLikes;
  503. });
  504. //Filter min locs
  505. filteredData = filteredData.filter((item) => {
  506. return item.coordinateCount >= minLocs;
  507. });
  508. //Filter min games played
  509. filteredData = filteredData.filter((item) => {
  510. return item.numberOfGamesPlayed >= minGamesPlayed;
  511. });
  512. //Filter min likes
  513. filteredData = filteredData.filter((item) => {
  514. return item.averageScore >= minAvgScore;
  515. });
  516.  
  517. //Filter official or not
  518. switch (localStorage.getItem("officialSetting")) {
  519. case "unofficial":
  520. filteredData = filteredData.filter((item) => item.isUserMap);
  521. break;
  522. case "official":
  523. filteredData = filteredData.filter((item) => !item.isUserMap);
  524. break;
  525. }
  526.  
  527. //Filter selectionMode
  528. switch (localStorage.getItem("selectionSetting")) {
  529. case "handpicked":
  530. filteredData = filteredData.filter((item) => item.locationSelectionMode == 1);
  531. break;
  532. case "polygonal":
  533. filteredData = filteredData.filter((item) => !item.isUserMap == 0);
  534. break;
  535. }
  536.  
  537. //Save the selected filters
  538. localStorage.setItem("minLikes", minLikes);
  539. localStorage.setItem("minLocs", minLocs);
  540. localStorage.setItem("minGamesPlayed", minGamesPlayed);
  541. localStorage.setItem("minAvgScore", minAvgScore);
  542.  
  543. return filteredData;
  544. }
  545.  
  546. createResultsFromSearch();
  547. newSearchPageCSS();
  548. createNewSearchbar();
  549.  
  550. document.querySelector(".search-input").addEventListener("keydown", function (event) {
  551. if (event.key === "Enter") {
  552. localStorage.setItem("searchTerm", document.querySelector(".search-input").value);
  553. createResultsFromSearch(localStorage.getItem("searchTerm"));
  554. }
  555. });
  556.  
  557. const officalSelectionBtns = document.querySelectorAll(".official-toggle-buttons > button");
  558. officalSelectionBtns.forEach((button) => {
  559. button.addEventListener("click", () => {
  560. officalSelectionBtns.forEach((a) => {
  561. a.classList.remove("active-button");
  562. });
  563. button.classList.add("active-button");
  564. let officialSetting = button.id.toString();
  565. localStorage.setItem("officialSetting", officialSetting);
  566. });
  567. });
  568. const selectionModeSelectionBtns = document.querySelectorAll(".selectionmode-toggle-buttons > button");
  569. selectionModeSelectionBtns.forEach((button) => {
  570. button.addEventListener("click", () => {
  571. selectionModeSelectionBtns.forEach((a) => {
  572. a.classList.remove("active-button");
  573. });
  574. button.classList.add("active-button");
  575. let selectionSetting = button.id.toString();
  576. localStorage.setItem("selectionSetting", selectionSetting);
  577. });
  578. });
  579.  
  580. const applyFiltersBtn = document.querySelector(".apply-button");
  581. applyFiltersBtn.addEventListener("click", () => createResultsFromSearch(localStorage.getItem("searchTerm")));
  582. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址