Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

False marker fixed #9

Open
CeresF3b opened this issue Dec 14, 2024 · 9 comments
Open

False marker fixed #9

CeresF3b opened this issue Dec 14, 2024 · 9 comments

Comments

@CeresF3b
Copy link

Hi, I saw your code and it looks extremely cool! I searched online and generally asked Copilot quite a bit to fix the “fake marker not appearing” bug. I was able to fix it with help mostly from Copilot. Below I have written a documentation and the corrected code, with also the errors I fixed. I hope it will be useful for you.

Detected Error:

The primary issue was that the "fake marker" did not appear on the Google map. This seemed to be caused by:

  1. Timing and Map Initialization: The Google map was not fully initialized at the time of creating the marker, which resulted in the failure of displaying the marker.
  2. Coordinate Update: The global coordinates (latitude and longitude) might not have been updated correctly or in a timely manner for the marker creation.

Applied Solution:

  1. Event Listener for Map Initialization: Added an event listener for the idle event of the Google map, ensuring that the marker is created only after the map is fully initialized. This ensures that the map is ready to receive the marker.
    google.maps.event.addListenerOnce(instance, 'idle', function() {
        console.log('Map is now idle and ready.');
        markers();
    });
  2. Coordinate and Retry Logic: Introduced checks to ensure the coordinates are valid and the map is ready before creating the marker. If the map is not initialized or the coordinates are not updated, the system attempts to create the marker again after a short interval.
    function markers() {
        if (typeof marker !== "undefined") {
            marker.setMap(null);
            marker = null;
        }
        map = window.myMap;
        if (map && globalCoordinates.lat !== 0 && globalCoordinates.lng !== 0) {
            optsMarker = {
                type: "drop",
                position: new google.maps.LatLng(globalCoordinates.lat, globalCoordinates.lng),
                clickable: false,
                map: map,
                id: "test"
            };
            marker = new google.maps.Marker(optsMarker);
            console.log('Marker created:', marker);
        } else {
            console.log('Map not initialized or coordinates not set. Retrying...');
            setTimeout(markers, 100); // Retry creating the marker after 100 ms
        }
    }

Corrected Code:

Here's the entire corrected code:

// ==UserScript==
// @name        Geotastic Helper
// @match       *://*.geotastic.net/*
// @grant       none
// @version     1.1
// @author      dharmik2319
// @license     GPLv3
// @description Helps you in the game Geotastic
// @run-at      document-start
// @icon        https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://geotastic.de&size=180
// ==/UserScript==

window.myMap;

let map;

// Hijacking the `google` module so that we can access an initialized google.maps.Map instance
var checkInterval = setInterval(function() {
    if (typeof google === 'object' && typeof google.maps === 'object' && typeof google.maps.Map === 'function') {
        var originalMap = google.maps.Map;
        google.maps.Map = function() {
            var instance = new originalMap(...arguments);
            window.myMap = instance
            google.maps.event.addListenerOnce(instance, 'idle', function() {
                console.log('Map is now idle and ready.');
                markers();
            });
            return instance;
        }
        clearInterval(checkInterval); // Stop checking once the module is loaded
    }
}, 10); // Check every 10 ms

let globalCoordinates = { // keep this stored globally, and we'll keep updating it for each API call.
    lat: 0,
    lng: 0
}

var originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
    if (url.startsWith('https://maps.googleapis.com/$rpc/google.internal.maps.mapsjs.v1.MapsJsInternalService/GetMetadata')) {
        this.addEventListener('load', function () {
            let interceptedResult = this.responseText;
            let parsed = interceptedResult.replace("null","");
            const pattern = /-*\d+\.\d+,-*\d+\.\d+/g;
            let match = parsed.match(pattern)[0];
            let split = match.split(",");

            let lat = Number.parseFloat(split[0]);
            let lng = Number.parseFloat(split[1]);
            globalCoordinates.lat = lat;
            globalCoordinates.lng = lng;
            markers();
        });
    }
    // Call the original open function
    return originalOpen.apply(this, arguments);
};

function mapsFromCoords() { // opens new Google Maps location using coords.
    const {lat, lng} = globalCoordinates;
    window.open(`https://www.google.com/maps/place/${lat},${lng}`);
}

let optsMarker;
let marker;
function markers() {
    if (typeof marker !== "undefined") {
        marker.setMap(null);
        marker = null;
    }
    map = window.myMap;
    if (map && globalCoordinates.lat !== 0 && globalCoordinates.lng !== 0) {
        optsMarker = {
            type: "drop",
            position: new google.maps.LatLng(globalCoordinates.lat, globalCoordinates.lng),
            clickable: false,
            map: map,
            id: "test"
        };
        marker = new google.maps.Marker(optsMarker);
        console.log('Marker created:', marker);
    } else {
        console.log('Map not initialized or coordinates not set. Retrying...');
        setTimeout(markers, 100); // Retry creating the marker after 100 ms
    }
}

let onKeyDown = (e) => {
    if (e.keyCode === 50) {
        mapsFromCoords();
    }
    if (e.keyCode === 49) {
        alert(`${globalCoordinates.lat}, ${globalCoordinates.lng}`);
    }
}
document.addEventListener("keydown", onKeyDown);
@dharmik2319
Copy link
Owner

Thanks a lot @CeresF3b! Have you tried to test this code? and did it run consistently? if so, please open a pull request. I am actually too busy these days, and woll be for a few months. I too had written this script initially with help from AI, but soon realized that the solutions it gave were very bogus, but i did retain the ideas i got from it. I will also test this code if i get the time. You can open a pr by then.

@dharmik2319
Copy link
Owner

The main problem was that Geotastic had changed, while I had quit playing, and so could not update the script. When it did work, it worked beautifully.

@dharmik2319
Copy link
Owner

Also, please open a PR to testing, not main

@CeresF3b
Copy link
Author

the code as you said doesn't always work 100% it is very buggy I can't say no I will try to find an alternative solution in the next few days.

@dharmik2319
Copy link
Owner

Sure, take your time. People have waited months, they surely can wait a few more. Also, it would be easier and better for you to fork the repo and do the changes there itself.

@CeresF3b
Copy link
Author

CeresF3b commented Dec 16, 2024

alright, I saw in general the code seems to work but the “fake marker” appears when he wants it I should figure out how to fix the bug but not being a professional in programming I will have to rely a lot on Copilot Chatgpt and in general Reddit with the help of others who surely know more than me anyway the general code works but the “fake marker” as I said before does not always appear it is definitely unstable

@dharmik2319
Copy link
Owner

It's alright, you can open a pull request with your current progress for now, it will be of great help. I will see if i can fix it.

@CeresF3b
Copy link
Author

CeresF3b commented Feb 4, 2025

Sorry for the delay, but I had some issues in my life outside the internet.

Anyway, I believe I have truly fixed the "problem" the script had with DeepSeek. I repeat, I am not a programmer at all, but I did some testing, and the script works. Below, I will provide everything DeepSeek wrote me, along with descriptions, etc.

  1. Fixed the Race Condition in Map Hijacking

Problem: The original code used setInterval to check for google.maps.Map, which could fail if the map initialized too quickly.
Solution:

Replaced setInterval with a MutationObserver to detect when the map container is added to the DOM.
Ensured hijacking happens only after the map container exists, eliminating the race condition.

const observer = new MutationObserver((mutations, obs) => {
if (hijackMap()) {
obs.disconnect(); // Stop observing once hijacked
}
});
observer.observe(document.body, { childList: true, subtree: true });

  1. Improved Response Parsing

Problem: The original regex /-\d+.\d+,-\d+.\d+/g failed for positive coordinates (e.g., 48.8584,2.2945 for Paris).
Solution:

Updated the regex to /[-+]?\d+\.\d+,[-+]?\d+\.\d+/g to handle both positive and negative coordinates.

const pattern = /[-+]?\d+.\d+,[-+]?\d+.\d+/g;
const match = response.match(pattern);

  1. Fixed Marker Ghosting

Problem: The original code only cleared the last marker, leaving multiple markers on the map during rapid updates.
Solution:

Tracked all markers in an array and cleared them before placing a new one.

let markerArray = [];
const placeMarker = () => {
markerArray.forEach(m => m.setMap(null)); // Clear existing markers
markerArray = [];
const marker = new google.maps.Marker({
position: { lat: globalCoordinates.lat, lng: globalCoordinates.lng },
map: map,
clickable: false
});
markerArray.push(marker);
};

  1. Made API Interception Stealthier

Problem: Overwriting XMLHttpRequest.prototype.open is detectable and leaves traces.
Solution:

Used an ES6 Proxy to intercept API calls without modifying the prototype directly.

const originalXHR = XMLHttpRequest;
XMLHttpRequest = new Proxy(originalXHR, {
construct(target, args) {
const xhr = new target(...args);
const handler = {
apply: function(target, thisArg, args) {
const [method, url] = args;
if (url.includes('GetMetadata')) {
xhr.addEventListener('load', function() {
const response = this.responseText;
const pattern = /[-+]?\d+.\d+,[-+]?\d+.\d+/g;
const match = response.match(pattern);
if (match) {
const [lat, lng] = match[0].split(',').map(Number.parseFloat);
globalCoordinates.lat = lat;
globalCoordinates.lng = lng;
placeMarker();
}
});
}
return target.apply(thisArg, args);
}
};
xhr.open = new Proxy(xhr.open, handler);
return xhr;
}
});

  1. Added Keybindings with Modifiers

Problem: The original keybindings (keyCode 49 and 50) could conflict with Geotastic's shortcuts.
Solution:

Used Ctrl+Shift+1/2 to avoid conflicts and make the keybindings harder to trigger accidentally.

document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.shiftKey) {
if (e.key === '1') alertCoordinates();
if (e.key === '2') openGoogleMaps();
}
});

  1. Optional Auto-Submit Feature

Problem: The original code lacked automation for submitting guesses.
Solution:

Added an optional autoSubmitGuess function to automatically submit coordinates after a delay.

const autoSubmitGuess = () => {
const guessButton = document.querySelector('[data-qa="guess-button"]');
if (guessButton) {
setTimeout(() => guessButton.click(), 1000); // Mimic human delay
}
};
// Uncomment to enable:
// autoSubmitGuess();

  1. General Code Cleanup

    Wrapped the entire script in an IIFE (Immediately Invoked Function Expression) to avoid polluting the global scope.
    Used const/let instead of var for better scoping.
    Added comments for clarity and maintainability.

Full Code:

// ==UserScript==
// @name Geotastic Dominator
// @match ://.geotastic.net/*
// @grant none
// @Version 1.1
// @author dharmik2319
// @license GPLv3
// @description Crush Geotastic with automated precision.
// @run-at document-start
// @ICON https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://geotastic.de&size=180
// ==/UserScript==

(function() {
'use strict';

// Global state
let globalCoordinates = { lat: 0, lng: 0 };
let markerArray = [];
let map;

// Hijack google.maps.Map initialization
const hijackMap = () => {
    if (typeof google === 'object' && typeof google.maps === 'object' && typeof google.maps.Map === 'function') {
        const originalMap = google.maps.Map;
        google.maps.Map = function(...args) {
            const instance = new originalMap(...args);
            map = instance;
            return instance;
        };
        return true;
    }
    return false;
};

// Use MutationObserver to ensure hijacking happens after map container is loaded
const observer = new MutationObserver((mutations, obs) => {
    if (hijackMap()) {
        obs.disconnect(); // Stop observing once hijacked
    }
});
observer.observe(document.body, { childList: true, subtree: true });

// Intercept API calls to extract coordinates
const originalXHR = XMLHttpRequest;
XMLHttpRequest = new Proxy(originalXHR, {
    construct(target, args) {
        const xhr = new target(...args);
        const handler = {
            apply: function(target, thisArg, args) {
                const [method, url] = args;
                if (url.includes('GetMetadata')) {
                    xhr.addEventListener('load', function() {
                        const response = this.responseText;
                        const pattern = /[-+]?\d+\.\d+,[-+]?\d+\.\d+/g;
                        const match = response.match(pattern);
                        if (match) {
                            const [lat, lng] = match[0].split(',').map(Number.parseFloat);
                            globalCoordinates.lat = lat;
                            globalCoordinates.lng = lng;
                            placeMarker();
                        }
                    });
                }
                return target.apply(thisArg, args);
            }
        };
        xhr.open = new Proxy(xhr.open, handler);
        return xhr;
    }
});

// Place a marker on the map
const placeMarker = () => {
    if (!map) return;
    markerArray.forEach(m => m.setMap(null)); // Clear existing markers
    markerArray = [];
    const marker = new google.maps.Marker({
        position: { lat: globalCoordinates.lat, lng: globalCoordinates.lng },
        map: map,
        clickable: false
    });
    markerArray.push(marker);
};

// Open Google Maps with current coordinates
const openGoogleMaps = () => {
    const { lat, lng } = globalCoordinates;
    window.open(`https://www.google.com/maps/place/${lat},${lng}`);
};

// Alert current coordinates
const alertCoordinates = () => {
    alert(`${globalCoordinates.lat}, ${globalCoordinates.lng}`);
};

// Keybindings (Ctrl+Shift+1 for coords, Ctrl+Shift+2 for Google Maps)
document.addEventListener('keydown', (e) => {
    if (e.ctrlKey && e.shiftKey) {
        if (e.key === '1') alertCoordinates();
        if (e.key === '2') openGoogleMaps();
    }
});

// Auto-submit guess (optional, uncomment to enable)
/*
const autoSubmitGuess = () => {
    const guessButton = document.querySelector('[data-qa="guess-button"]');
    if (guessButton) {
        setTimeout(() => guessButton.click(), 1000); // Mimic human delay
    }
};
autoSubmitGuess();
*/

})();

@dharmik2319
Copy link
Owner

Next time, write code surrounded with ```. Secondly, thanks for your work, but I really won't be able to test this for at least a month or so

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants