您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a trace of where you have been to GeoGuessr's results screen
- // ==UserScript==
- // @name GeoGuessr Path Logger
- // @version 0.4.3
- // @description Adds a trace of where you have been to GeoGuessr's results screen
- // @match https://www.geoguessr.com/*
- // @author victheturtle#5159
- // @license MIT
- // @run-at document-start
- // @require https://openuserjs.org/src/libs/xsanda/Run_code_as_client.js
- // @require https://openuserjs.org/src/libs/xsanda/Google_Maps_Promise.js
- // @namespace https://gf.qytechs.cn/users/967692-victheturtle
- // ==/UserScript==
- // credits to xsanda (https://openuserjs.org/users/xsanda) for the original GeoGuessr Path Logger script
- /*jshint esversion: 6 */
- googleMapsPromise.then(() => runAsClient(() => {
- const google = window.google;
- const KEEP_FOR = 1000 * 60 * 60 * 24 * 7 * 4; // 4 weeks
- // Keep a track of the lines drawn on the map, so they can be removed
- let markers = [];
- const isGamePage = () => location.pathname.includes("/challenge/") || location.pathname.includes("/results/") || location.pathname.includes("/game/") ||
- location.pathname.includes("/duels/") || location.pathname.includes("/team-duels/");
- // Detect if only a single result is shown
- const singleResult = () => !!document.querySelector('div[class^="round-result_distanceIndicatorWrapper__"]') ||
- (!!document.querySelector('[class^="overlay_backdrop__"], [class^="overlay_overlay__"]') && !document.querySelector('[class^=new-round_roundNumber__]') &&
- !document.querySelector('[class^=new-game_container__]')) ||
- (!!document.querySelector('div[class^="result-layout_root__"]') && !document.querySelector('div[class^="result-layout_content"]'));
- // Detect if a results screen is visible, so the traces should be shown
- const resultShown = () => singleResult() || !!document.querySelector('div[class^="result-overlay_overlayTotalScore__"]') || location.href.includes('results') ||
- !!document.querySelector('[class^="game-summary_container__"]') || !!document.querySelector('div[class^="result-layout_root__"]') || location.href.includes('summary');
- // Keep a track of whether we are in a round already
- let inGame = false;
- // Get the game ID, for storing the trace against
- const id = () => {
- const split = location.href.split("/")
- if (split[split.length-1] == "summary") return split[split.length-2]
- else return split[split.length-1]
- }
- const roundNumber = () => {
- const el = document.querySelector('[data-qa=round-number] :nth-child(2)');
- const el2 = document.querySelector('[class^=round-score_roundNumber__]');
- return el ? parseInt(el.innerHTML) : (el2 ? parseInt(el2.innerHTML.split(" ")[1]) : 0);
- };
- const roundID = (n, gameID) => (gameID || id()) + '-' + (n || roundNumber());
- // Get the location of the street view
- const getPosition = sv => ({
- lat: sv.position.lat(),
- lng: sv.position.lng(),
- });
- // Record the time a game was played
- const updateTimestamp = () => {
- const timestamps = JSON.parse(localStorage.timestamps || "{}");
- timestamps[id()] = Date.now();
- localStorage.timestamps = JSON.stringify(timestamps);
- };
- // Remove all games older than a week
- const clearOldGames = () => {
- const timestamps = JSON.parse(localStorage.timestamps || "{}");
- // Delete all games older than a week
- const cutoff = Date.now() - KEEP_FOR;
- for (const [gameID, gameTime] of Object.entries(timestamps)) {
- if (gameTime < cutoff) {
- delete timestamps[gameID];
- Object.keys(localStorage).filter(key => key.startsWith(gameID)).forEach(key => delete localStorage[key]);
- }
- }
- localStorage.timestamps = JSON.stringify(timestamps);
- };
- const R = 6371.071; // radius of the Earth
- const distance = (mk1lat, mk1lng, mk2lat, mk2lng) => {
- const rlat1 = mk1lat * (Math.PI / 180)
- const rlat2 = mk2lat * (Math.PI / 180)
- const difflat = rlat2 - rlat1
- const difflon = (mk2lng - mk1lng) * (Math.PI / 180);
- const km = 2*R * Math.asin(Math.sqrt(
- Math.sin(difflat/2) * Math.sin(difflat/2) +
- Math.cos(rlat1) * Math.cos(rlat2) * Math.sin(difflon/2) * Math.sin(difflon/2)
- ))
- return km;
- }
- clearOldGames();
- // Keep a track of the current round’s route
- let route;
- let currentRound = undefined;
- // Keep a track of the start location for the current round, for detecting the return to start button
- let start;
- let lastPosition = undefined;
- // Handle the street view being navigated
- const onMove = (sv) => {
- try {
- if (!isGamePage()) return;
- const position = getPosition(sv);
- if (!inGame) {
- // Do nothing if the map is being updated in the background, e.g. on page load while the results are still shown
- if (resultShown()) return;
- // otherwise start the round
- inGame = true;
- start = position;
- route = [];
- } else if (currentRound !== roundID()) {
- currentRound = roundID();
- start = position;
- route = [];
- }
- // If we’re at the start or moving too far in one click, assume the flag or checkpoint feature were used and begin a new trace
- if (position.lat == start.lat && position.lng == start.lng ||
- lastPosition != undefined && distance(lastPosition.lat, lastPosition.lng, position.lat, position.lng) > 0.2) {
- start = position;
- route.push([]);
- }
- lastPosition = position;
- // Add the location to the trace
- route[route.length - 1].push(position);
- }
- catch (e) {
- console.error("GeoGuessr Path Logger Error:", e);
- }
- };
- let mapState = 0;
- // The geometry API isn’t loaded unless a Street View has been displayed since the last load.
- const loadGeometry = () => new Promise((resolve, reject) => {
- const existingScript = document.querySelector("script[src^='https://maps.googleapis.com/maps-api-v3/api/js/']")
- if (!existingScript) reject("No Google Maps loaded yet");
- const libraryURL = existingScript.src.replace(/(.+\/)(.+?)(\.js)/, '$1geometry$3');
- document.head.appendChild(Object.assign(document.createElement("script"), {
- onload: resolve,
- type: "text/javascript",
- src: libraryURL,
- }));
- });
- const onMapUpdate = (map) => {
- try {
- if (!isGamePage()) return;
- if (!google.maps.geometry) {
- loadGeometry().then(() => onMapUpdate(map));
- return;
- }
- // create a checksum of the game state, only updating the map when this changes, to save on computation
- const newMapState = (inGame ? 50 : 0) + (resultShown() ? 100 : 0) + (singleResult() ? 200 : 0) + roundNumber();
- if (newMapState == mapState) return;
- mapState = newMapState;
- // Hide all traces
- markers.forEach(m => m.setMap(null));
- // If we’re looking at the results, draw the traces again
- if (resultShown()) {
- // If we were in a round the last time we checked, then we need to save the route
- if (inGame) {
- // encode the route to reduce the storage required.
- const encodedRoutes = route.map(path => google.maps.geometry.encoding.encodePath(path.map(point => new google.maps.LatLng(point))));
- localStorage[roundID()] = JSON.stringify(encodedRoutes);
- updateTimestamp();
- }
- inGame = false;
- // Show all rounds for the current game when viewing the full results
- const roundsToShow = singleResult() ? [roundID()] : Object.keys(localStorage).filter(map => map.startsWith(id()));
- markers = roundsToShow
- .map(key => localStorage[key]) // Get the map for this round
- .filter(r => r) // Ignore missing rounds
- .flatMap(r =>
- // Render each trace within each round as a red line
- JSON.parse(r).map(polyline =>
- new google.maps.Polyline({
- path: google.maps.geometry.encoding.decodePath(polyline),
- geodesic: true,
- strokeColor: '#FF0000',
- strokeOpacity: 1.0,
- strokeWeight: 2,
- })
- )
- );
- // Add all traces to the map
- markers.forEach(m => m.setMap(map));
- }
- }
- catch (e) {
- console.error("GeoGuessr Path Logger Error:", e);
- }
- };
- // When a StreetViewPanorama is constructed, add a listener for moving
- const oldSV = google.maps.StreetViewPanorama;
- google.maps.StreetViewPanorama = Object.assign(function (...args) {
- const res = oldSV.apply(this, args);
- this.addListener('position_changed', () => onMove(this));
- return res;
- }, {
- prototype: Object.create(oldSV.prototype)
- });
- // When a Map is constructed, add a listener for updating
- const oldMap = google.maps.Map;
- google.maps.Map = Object.assign(function (...args) {
- const res = oldMap.apply(this, args);
- this.addListener('idle', () => onMapUpdate(this));
- return res;
- }, {
- prototype: Object.create(oldMap.prototype)
- });
- }));
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址