A Better Search

My attempt to improve the search function in geoguessr.

当前为 2024-02-09 提交的版本,查看 最新版本

// ==UserScript==
// @name         A Better Search
// @namespace    http://tampermonkey.net/
// @version      0.0.1
// @description  My attempt to improve the search function in geoguessr.
// @author       Lemson
// @match        https://www.geoguessr.com/
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @license      MIT
// @grant        GM_addStyle
// ==/UserScript==

const CSS = `
.top-search-bar{
  width: 30%;
  margin-left: 34%;
  position: absolute;
  border-radius: 2rem;
  padding-left: 1rem;
  font-size: 1rem;
  background-color: rgba(0,0,0,.8);
  color: white;
  border: 1px solid white;
}

.modal {
  width: 70%;
  height: 70%;
  background-color: #d9d9d9;
  border-radius: 2.5rem;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  scale: 0;
  transition: 0.1s;
  box-shadow: 0 0 5rem 1rem rgb(0, 0, 0);
  display: flex;
  z-index:5;
}
.active {
  transition: 0.1s;
  scale: 1;
}
.sidebar {
  height: 100%;
  width: 4rem;
  background-color: #545454;
  border-top-left-radius: 2.5rem;
  border-bottom-left-radius: 2.5rem;
  border-right: 1px solid black;
}
.maincontent {
  display: flex;
  flex-direction: column;
  width: 100%;
}
.search {
  height: 2.5rem;
  margin: 2rem;
  display: flex;
  justify-content: space-between;
}
.searchbar {
  width: 50%;
  border-radius: 1.1rem;
  border: 0.1rem solid black;
  padding-left: 1rem;
  box-shadow: 0 0 7rem black;
  font-size: 1rem;
}
.filter-btn {
    height: 100%;
    width: 2.5rem;
    border-radius: 0.4rem;
    border: 1px solid black;
    background-color: rgb(192, 192, 192);
  }
.filter-btn:active {
  background-color: rgb(145, 145, 145);
}
.close {
    margin-left: 5rem;
    font-size: 2rem;
    cursor: pointer;
  }
.close:hover{
  transition: .2s;
  scale: 1.1;
}
.windows {
  width: 95%;
  height: 80%;
  margin-left: 2rem;
  border: 1px solid black;
  display: flex;
  flex-direction: row;
  justify-content: space-evenly;
}
.results {
  background-color: rgba(0, 0, 0, 0.112);
  width: 100%;
  height: 100%;
  overflow: auto;
  overflow-x: hidden;
}
.selection-info {
  background-color: rgba(0, 0, 0, 0.112);
  border-left: 0.1rem solid black;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow: auto;
  overflow-x: hidden;
}
.result-container {
  width: 99%;
  border: 1px solid rgb(0, 0, 0);
  margin: 0.2rem;
  display: flex;
}
.result-container:hover {
  background-color: rgba(0, 0, 0, 0.107);
}
.result-container:active {
  background-color: rgba(0, 0, 0, 0.25);
}
.result-title-author {
  width: 90%;
}
.result-title {
  font-size: 1.6rem;
}
.result-creator {
  font-size: 1rem;
}
.author-space {
  text-decoration: underline;
  color: rgb(15, 0, 88);
}
.likesdisplay {
  width: 10%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: red;
  font-size: 1.2rem;
  -webkit-text-stroke: .5px black;
}
.selected {
  background-color: rgba(0, 0, 255, 0.1);
}
.selected-header {
  display: flex;
  flex-direction: row;
  padding-left: 1rem;
  border-bottom: .1rem solid black;
  justify-content: space-between;
  padding-right: 1rem;
}
.header-likes {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.selected-title {
  font-size: 1.4rem;
}
.selected-creator {
  font-size: .8rem;
  font-weight: bold;
  color: black;
}
.selected-creator:hover{
  color: blue;
}
.selected-desc {
  margin: .5rem;
  border: 1px solid black;
  padding: .3rem;
  border-radius: .5rem;
  height: auto;
  min-height: 2rem;
}
.tags {
  display: flex;
  flex-direction: row;
  gap: .7rem;
  margin-left: .5rem;
  flex-wrap: wrap;
  row-gap: 0.5rem;
}
.searchAndFilter{
  width: 100%;
}
.tag{
  border: 1px solid black;
  padding: .2rem;
  padding-left: .5rem;
  padding-right: .5rem;
  border-radius: 2rem;
  height: .9rem;
  font-size: .8rem;
}
.game {
  position: relative;
  width: 100%;
  height: 100%;
  margin: 0.5rem;
  margin-top: 1.5rem;
  border-radius: 1rem;
  border: 1px solid black;
  overflow: hidden;
  display: flex; /* Use flexbox layout */
}

.game:before {
  content: "";
  background-image: url('https://img.freepik.com/free-photo/planet-earth-background_23-2150564685.jpg');
  background-size: cover;
  background-position: center;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: -10;
  overflow: hidden;
}
.game-settings {
  width: 50%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.ol-selected-leaderboard{
  width: 50%;
  height: 100%;
}
::-webkit-scrollbar {
  width: 0.1rem;
}
::-webkit-scrollbar-thumb {
  background-color: rgb(0, 0, 0);
}
.game-mode-select{
  color: white;
  display: flex;
  align-items: center;
  flex-direction: column;
  margin-top: .6rem;
  width: 100%;
  justify-content: center;
  margin-top: .7vh;
}
.game-mode-select>h1{
  font-weight: 500;
  letter-spacing: .06vw;
  margin-bottom: .4vh;
  font-size: 1.6vh;
}
.gamemode-buttons{
  display: flex;
  flex-direction: row;
  justify-content: space-evenly;
  background-color: rgba(255, 255, 255, 0.06);
  border: .1rem solid white;
  border-radius: .4rem;
  width: 95%;
  text-wrap: nowrap;
}
.gamemode-buttons>*{
  font-size: 1.2rem;
  color: white;
  padding-left: 1rem;
  padding-right: 1rem;
  border-radius: .5rem;
  margin: .2rem;
  font-style: italic;
}

.selected-mode {
  background-color: #7950E5;
}

.round-select{
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  margin-top: 1.2rem;
  width: 100%;
}
.round-select>h1{
  font-size: 1.3rem;
  font-weight: 500;
  letter-spacing: .1rem;
  margin-bottom: .3rem;
}
.round-numbers{
  display: flex;
  flex-direction: row;
  justify-content: space-evenly;
  background-color: rgba(255, 255, 255, 0.06);
  border: .1rem solid white;
  border-radius: .4rem;
  width: 95%;
  height: 3rem;
}
.round-numbers>*{
  color: white;
  width: 3.5rem;
  margin-top: .2rem;
  margin-bottom: .2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: .5rem;
}


.round-time{
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  margin-top: 1.2rem;
  width: 95%;
  background-color: rgba(0,0,0,0.7);;
  border: .1rem solid white;
  border-radius: .5rem;
  padding-top: 1rem;
  padding-bottom: 1rem;
}
.round-time>h1, .round-time>h2{
  font-size: 1.3rem;
  font-weight: 500;
  letter-spacing: .1rem;
  margin-bottom: .3rem;
}
.time-slider{
  width: 90%;
  padding:0;
}


.start-game-btn{
  width: 95%;
  height: 4rem;
  background-color: #97E851;
  border-radius: 2rem;
  box-shadow: inset 0 1px 10px white;
  font-size: 2rem;
  font-weight: bold;
  font-style: italic;
  color: white;
  -webkit-text-stroke: 1px black;
  margin-top: 1rem;
  border: .1rem solid white;
}

`;
GM_addStyle(CSS);
const HTML = `
<div id="overlay-main" class="modal">
      <div class="sidebar"></div>
      <div class="maincontent">
        <div class="search">
          <div class="searchAndFilter">
          <input type="text" placeholder="Search..." class="searchbar" />
          <button class="filter-btn">Filter</button>
          </div>

          <button class="close">✖</button>
        </div>
        <div class="windows">
          <div class="results">
            <!--
              <div class="result-container">
              <div class="result-title-author">
                <h1 class="result-title">A Community Turkey</h1>
                <h2 class="result-creator">created by: <a href="google.com">John Harvey Kellogg</a></h2>
              </div>
              <div class="likesdisplay">
                <div class="heartSVG">♥</div>
                <h3 class="like-ount"></h3>
              </div>
            </div>
            -->
          </div>
          <div class="selection-info">
          <!--
            <header class="selected-header">
              <div class="header-title-container">
                <h1 class="selected-title">A Balanced Turkey</h1>
                <a class="selected-creator  ">John Harvey Kellogg</a>
              </div>
              <div class="header-likes">
                <h3 class="header-likes-num">69</h3>
                <div>♥</div>
              </div>
            </header>
            -->
          </div>
        </div>
      </div>
    </div>
`;
let div = document.createElement("div");
div.innerHTML = HTML;
document.body.append(div);

//******************************************************************* */
//******************************************************************* */
//******************************************************************* */
//******************************************************************* */
//Creates searchbar on the top.
//header_logoWrapper__
let searchBarParent = document.querySelector("[class^='header_logoWrapper__']");
searchBarParent.style = "justify-content: space-between;";
let topSearchBar = document.createElement("input");
topSearchBar.placeholder = "Search for a map...";
topSearchBar.classList.add("top-search-bar");
searchBarParent.appendChild(topSearchBar);
const everything = document.querySelector(".modal");

document.addEventListener("keydown", function (event) {
  if (document.activeElement == topSearchBar && event.key === "Enter") {
    let searchTerm = topSearchBar.value;
    everything.classList.add("active");
    search(searchTerm);
  }
});

//
const resultsBox = document.querySelector(".results");
const infoBox = document.querySelector(".selection-info");
const overlay = document.querySelector(".modal");
const closeButton = document.querySelector(".close");
const searchBar = document.querySelector(".searchbar");
let chosenMode = "no move";
let roundAmount = 5;
let roundTimeSeconds = 300;

const startGameBaseUrl = "https://www.geoguessr.com/api/v3/games";

closeButton.addEventListener("click", function () {
  overlay.classList.remove("active");
});

const search = async (searchTerm) => {
  //let searchTerm = document.querySelector(".searchbar").value;

  let resp = await fetch(`https://www.geoguessr.com/api/v3/search/map?page=0&count=20&q=${searchTerm}`);
  //let resp = await fetch(`https://www.geoguessr.com/api/v3/search/map?page=0&count=1&q=a%balanced%turkey`);
  if (!resp) {
    console.error("fetch error");
  }
  const data = await resp.json();
  console.log("Search results:", data);
  createResults(data);
};

const createResults = (searchData) => {
  //removes any HTML, if there is any. So that new HTML can be added.
  resultsBox.innerHTML = null;

  searchData.forEach((item) => {
    //Create and add the HTML for result
    let resultContainerHTML = `
    <div class="result-container">
      <div class="result-title-author">
        <h1 class="result-title">${item.name}</h1>
        <h2 class="result-creator">created by: <a target="_blank" id="author-link" href="user/${item.creatorId}">${item.creator}</a></h2>
      </div>
      <div class="likesdisplay">
        <div class="heartSVG">♥</div>
        <h3 class="like-ount">${item.likes}</h3>
      </div>
    </div>`;
    //Create element to append the created HTML onto
    let resultContainerParent = document.createElement("div");
    resultContainerParent.innerHTML = resultContainerHTML;
    resultsBox.appendChild(resultContainerParent);

    //click on result
    resultContainerParent.addEventListener("click", function () {
      let resultContainer = resultContainerParent.querySelector(".result-container");
      let clearSelections = document.querySelectorAll(".result-container");

      clearSelections.forEach((thing) => {
        //Remove selections
        thing.classList.remove("selected");
      });
      displaySelectedMap(item, resultContainer);
    });
  });
};

const startGame = (map) => {
  console.log("start game");
  console.log(chosenMode);
  let gameSettings;
  if (chosenMode == "move") {
    gameSettings = {
      forbidMoving: false,
      forbidRotating: false,
      forbidZooming: false,
      map: map,
      rounds: roundAmount,
      timeLimit: roundTimeSeconds,
    };
  }
  if (chosenMode == "no move") {
    gameSettings = {
      forbidMoving: true,
      forbidRotating: false,
      forbidZooming: false,
      map: map,
      rounds: roundAmount,
      timeLimit: roundTimeSeconds,
    };
  }
  if (chosenMode == "nmpz") {
    gameSettings = {
      forbidMoving: true,
      forbidRotating: true,
      forbidZooming: true,
      map: map,
      rounds: roundAmount,
      timeLimit: roundTimeSeconds,
    };
  }

  fetch(startGameBaseUrl, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(gameSettings),
  })
    .then((response) => response.json())
    .then((data) => {
      console.log(`https://www.geoguessr.com/game/${data.token}`);
      return data; // Pass the data object to the next then block
    })
    .then((data) => {
      window.location.href = `https://www.geoguessr.com/game/${data.token}`;
    })
    .catch((error) => console.error("error", error));
};

const setMode = (mode, buttons, button) => {
  buttons.forEach((a) => {
    a.classList.remove("selected-mode");
  });
  switch (mode) {
    case "MOVE":
      chosenMode = "move";
      button.classList.add("selected-mode");
      break;
    case "NO MOVE":
      chosenMode = "no move";
      button.classList.add("selected-mode");
      break;
    case "NMPZ":
      chosenMode = "nmpz";
      button.classList.add("selected-mode");
      break;
  }
  console.log(mode);
};

const setRounds = (roundNum, nums, selectedNum) => {
  nums.forEach((a) => {
    a.classList.remove("selected-mode");
  });

  switch (roundNum) {
    case "1":
      roundAmount = 1;
      selectedNum.classList.add("selected-mode");
      break;
    case "2":
      roundAmount = 2;
      selectedNum.classList.add("selected-mode");
      break;
    case "3":
      roundAmount = 3;
      selectedNum.classList.add("selected-mode");
      break;
    case "4":
      roundAmount = 4;
      selectedNum.classList.add("selected-mode");
      break;
    case "5":
      roundAmount = 5;
      selectedNum.classList.add("selected-mode");
      break;
  }
  console.log(roundNum);
};

const displaySelectedMap = (item, resultContainer) => {
  resultContainer.classList.add("selected");

  //clear the infobox
  infoBox.innerHTML = null;
  if (item.description == "" || item.description == null) {
    item.description = "The creator of this map has not added a description";
  }

  item.created = item.created.slice(0, 10);

  let officialState;
  if (!item.isUserMap) {
    officialState = "Official Map";
  } else {
    officialState = "Usermade Map";
  }

  let selectedItemHTML = `
  <header class="selected-header">
    <div class="header-title-container">
      <h1 class="selected-title">${item.name}</h1>
      <a class="selected-creator" href="user/${item.creatorId}" target="_blank">${item.creator}</a>
    </div>
    <div class="header-likes">
      <h3 class="header-likes-num">${item.likes}</h3>
      <div>♥</div>
    </div>
  </header>
  <div class="selected-info-container">
    <div class="selected-desc">${item.description}</div>
    <div class="tags">
      <div class="tag">
        <b>${item.coordinateCount} locations</b>
      </div>
      <div class="tag">
        <b>Created: ${item.created}</b>
      </div>
      <div class="tag">
        <b>${item.numberOfGamesPlayed} games played</b>
      </div>
      <div class="tag">
        <b>${officialState}</b>
      </div>
    </div>
  </div>
  <div class="game">
    <div class="game-settings">
      <div class="game-mode-select">
        <h1>Game settings</h1>
        <div class="gamemode-buttons">
          <button class="mode-button selected-mode">MOVE</button>
          <button class="mode-button">NO MOVE</button>
          <button class="mode-button">NMPZ</button>
        </div>
      </div>

      <div class="round-select">
        <h1>ROUNDS</h1>
        <div class="round-numbers">
          <button class="round-number ">1</button>
          <button class="round-number">2</button>
          <button class="round-number">3</button>
          <button class="round-number">4</button>
          <button class="round-number selected-mode">5</button>
        </div>
      </div>

      <div class="round-time">
        <h1>ROUND TIME</h1>
        <input type="range" min="1" max="600" step="1" class="time-slider" value="30"/>
        <h2 class="time-display">30 seconds</h2>
      </div>

      <button class="start-game-btn">START GAME!</button>



    </div>
    <div class="ol-selected-leaderboard"></div>
  </div>
  `;
  infoBox.innerHTML = selectedItemHTML;

  const slider = document.querySelector(".time-slider");
  const timeDisplay = document.querySelector(".time-display");

  slider.addEventListener("input", function () {
    if (slider.value < 60) {
      timeDisplay.textContent = `${slider.value} seconds`;
    } else if (slider.value >= 60) {
      let timeMin = Math.floor(slider.value / 60);
      let timeSec = slider.value % 60;
      if (timeSec == "0") timeDisplay.textContent = `${timeMin} minutes`;
      else timeDisplay.textContent = `${timeMin} minutes, ${timeSec} seconds`;
    }

    roundTimeSeconds = slider.value;
  });

  const startButton = document.querySelector(".start-game-btn");
  const modeButtons = document.querySelectorAll(".mode-button");
  const roundNumbers = document.querySelectorAll(".round-number");

  modeButtons.forEach((button) => {
    button.addEventListener("click", () => setMode(button.textContent, modeButtons, button));
  });

  roundNumbers.forEach((number) => {
    number.addEventListener("click", () => setRounds(number.textContent, roundNumbers, number));
  });

  //start button, get every piece of data that's needed to start a game on the currently selected settings.
  startButton.addEventListener("click", () => {
    //item.id is map
    startGame(item.id);
  });
};

document.addEventListener("keydown", function (event) {
  if (document.activeElement == searchBar && event.key === "Enter") {
    let searchTerm = searchBar.value;

    search(searchTerm);
  }
});

QingJ © 2025

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