// ==UserScript==
// @name GeoHintr
// @version 0.2
// @description GeoHintr - Allows you to play maps that contain hints on the locations including written hints or pictures or videos
// @author MrAmericanMike and Alok
// @include /^(https?)?(\:)?(\/\/)?([^\/]*\.)?geoguessr\.com($|\/.*)/
// @grant none
// @run-at document-start
// @namespace https://gf.qytechs.cn/users/250979
// ==/UserScript==
console.log("GeoHintr");
const GH = {
MYHINTS: [],
round: 0,
hintDiv: null,
uiDiv: null,
tokens: [],
setHints: (hints) => {
GH.MYHINTS = hints;
},
init: () => {
console.log("GH Init");
const targetNode = document.getElementsByTagName("body")[0];
const config = {
attributes: false,
childList: true,
subtree: false,
characterData: false
};
const observer = new MutationObserver((mutationsList, observer) => {
for (const mutation of mutationsList) {
if (mutation.type === "childList") {
GH.checkRound();
}
}
});
observer.observe(targetNode, config);
if (!GH.hintDiv) {
GH.hintDiv = document.createElement("div");
GH.hintDiv.setAttribute("class", "game-statuses");
GH.hintDiv.setAttribute("id", "GH-hint");
GH.hintDiv.setAttribute("style", "display: inline-flex; padding: 5px 0.5rem 0px; margin-top: 2px;");
GH.hintDiv.style.display = "none";
}
GH.handleStorage();
GH.createUI();
GH.keyListener();
},
handleStorage: () => {
GH.tokens = JSON.parse(sessionStorage.getItem("GH-TOKENS"));
if (GH.tokens && GH.tokens.length > 0) {
console.log("Tokens stored");
console.log(GH.tokens);
}
else {
sessionStorage.setItem("GH-TOKENS", JSON.stringify([]));
GH.tokens = JSON.parse(sessionStorage.getItem("GH-TOKENS"));
console.log("Tokens set");
console.log(GH.tokens);
}
},
storeTokens: () => {
sessionStorage.setItem("GH-TOKENS", JSON.stringify(GH.tokens));
console.log("Tokens stored");
console.log(GH.tokens);
},
removeToken: (token) => {
for (let i = 0; i < GH.tokens.length; i++) {
if (GH.tokens[i].token === token) {
GH.tokens.splice(i, 1);
}
}
GH.storeTokens();
GH.redrawUI();
},
checkRound: () => {
const roundData = document.querySelector("div[data-qa='round-number']");
if (roundData) {
let roundElement = roundData.querySelector(".game-status__body");
if (roundElement) {
let round = parseInt(roundElement.innerText.charAt(0));
if (!isNaN(round) && round >= 1 && round <= 5) {
if (round != GH.round) {
console.log("GH Round Changed");
GH.round = round;
GH.doMagic();
}
}
}
}
},
doMagic: () => {
let URL = null;
if (window.location.pathname.includes("game")) {
URL = `https://www.geoguessr.com/api/v3/games/${window.location.pathname.substring(6)}`;
}
else if (window.location.pathname.includes("challenge")) {
URL = `https://www.geoguessr.com/api/v3/challenges/${window.location.pathname.substring(11)}/game`;
}
if (URL) {
fetch(URL)
.then((response) => response.json())
.then((data) => {
const { lat, lng } = data.rounds[data.round - 1];
let coordinates = { lat, lng };
GH.checkForHints(coordinates);
})
.catch((error) => {
console.log("Something went wrong");
console.log(error);
});
}
},
checkForHints: (coordinates) => {
GH.MYHINTS.forEach(hint => {
if(GH.coordinatesInRange(coordinates, { lat: hint.lat, lng: hint.lng })){
GH.updateHint(hint);
}
});
},
updateHint: (hint) => {
GH.hintDiv.innerHTML = `
<div class="game-status__body">
<style>
#wrapper {
width: 25vw;
text-align: center;
}
</style>
<div id="wrapper">
<p id="hint"></p>
<div class="spacer" style="clear: both;"></div>
</div>
</div>
`;
if (document.getElementById("GH-hint")) {
GH.hintDiv.style.display = "";
}
else {
document.querySelector(".game-layout__status").appendChild(GH.hintDiv);
GH.hintDiv.style.display = "";
}
document.getElementById("hint").innerText = hint.hint;
if (hint.img) {
console.log(true);
let image = document.createElement("img");
image.src = hint.img;
image.style.maxHeight = "25vh";
document.getElementById("wrapper").appendChild(image);
}
if (hint.video) {
let iframe = document.createElement("iframe");
iframe.src = `https://www.youtube.com/embed/${hint.video}`;
iframe.width = "100%";
iframe.style.minHeight = "25vh";
iframe.setAttribute("allowfullscreen", "");
iframe.setAttribute("frameborder", "0");
document.getElementById("wrapper").appendChild(iframe);
}
},
loadHints: (token, name) => {
document.getElementById("GH-message").innerText = "Loading...";
fetch(`https://dongles.vercel.app/dongle/paste`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ token: token })
})
.then((results) => {
return results.json();
})
.then((data) => {
if (data.error) {
console.log(data);
throw new Error(data.error.message);
}
GH.setHints(data);
document.getElementById("GH-message").innerText = "Hints loaded...";
GH.doMagic();
setTimeout(() => {
GH.hideUI();
}, 2000);
})
.catch((error) => {
console.log(error);
document.getElementById("GH-message").innerText = "Something went wrong...";
});
let exists = false;
GH.tokens.forEach((tok) => {
if(tok.token === token) {
exists = true;
}
});
if(!exists){
GH.tokens.push({ token, name });
}
GH.storeTokens();
GH.redrawUI();
},
coordinatesInRange: (original, hint) => {
let ky = 40000 / 360;
let kx = Math.cos(Math.PI * hint.lat / 180.0) * ky;
let dx = Math.abs(hint.lng - original.lng) * kx;
let dy = Math.abs(hint.lat - original.lat) * ky;
return Math.sqrt(dx * dx + dy * dy) <= 0.050;
},
keyListener: () => {
document.addEventListener("keydown", (event) => {
if (event.code === "KeyH" && event.shiftKey && event.altKey && !event.ctrlKey && !event.metaKey && !event.repeat) {
if (GH.uiDiv.style.display === "block") {
GH.hideUI();
}
else {
GH.showUI();
}
}
});
},
createUI: () => {
if (!GH.uiDiv) {
GH.uiDiv = document.createElement("div");
GH.uiDiv.setAttribute("id", "GH-ui")
Object.assign(GH.uiDiv.style, {
display: "none",
position: "fixed",
backgroundColor: "#eee9e0",
zIndex: "1000",
width: "fit-content",
height: "fit-content",
top: "48px",
left: "8px",
padding: "20px",
borderRadius: "10px",
boxShadow: "0 2px 2px 0",
overflow: "hidden"
});
GH.uiDiv.innerHTML = ``;
GH.tokens.forEach((token) => {
GH.uiDiv.innerHTML += `
<input type="text" size="10" id="${token.token}" value="${token.token}" />
<input type="text" value="${token.name}" />
<button id="GH-${token.token}">LOAD</button>
<button id="GHR-${token.token}">X</button>
<br />
`;
});
GH.uiDiv.innerHTML += `
<input type="text" placeholder="Pastebin Token" size="10" id="pastebin_token" />
<input type="text" placeholder="Description" id="pastebin_name" />
<button id="GH-load">LOAD</button>
<br />
`;
GH.uiDiv.innerHTML += `
<div style="text-align: center; margin-top: 8px">
<p id="GH-message"></p>
</div>
`;
}
document.body.appendChild(GH.uiDiv);
document.getElementById("GH-load").addEventListener("click", () => {
GH.loadHints(document.getElementById("pastebin_token").value, document.getElementById("pastebin_name").value);
});
GH.tokens.forEach((token) => {
document.getElementById(`GH-${token.token}`).addEventListener("click", () => GH.loadHints(token.token, token.name));
});
GH.tokens.forEach((token) => {
document.getElementById(`GHR-${token.token}`).addEventListener("click", () => GH.removeToken(token.token));
});
},
redrawUI: () => {
GH.hideUI();
document.getElementById("GH-ui").remove();
GH.uiDiv = null;
GH.createUI();
GH.showUI();
},
showUI: () => {
GH.uiDiv.style.display = "block";
},
hideUI: () => {
GH.uiDiv.style.display = "none";
}
}
GH.init();