Sniffies Integrated Location Spoofer

Integrate location spoofing with Sniffies' existing Travel Mode button using natural language inputs. Disable native Travel Mode and provide a custom spoofing popup with reset functionality in dark mode, including customizable quick location presets and an integrated spoofing indicator.

2024-12-15 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Sniffies Integrated Location Spoofer
// @namespace    https://sniffies.com/
// @version      2.8
// @description  Integrate location spoofing with Sniffies' existing Travel Mode button using natural language inputs. Disable native Travel Mode and provide a custom spoofing popup with reset functionality in dark mode, including customizable quick location presets and an integrated spoofing indicator.
// @author       Your Name
// @match        https://sniffies.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // Constants
    const STORAGE_KEY = 'sniffiesLocationSpoofer';
    const PRESETS_KEY = 'sniffiesLocationPresets';
    const GEOCODING_API_URL = 'https://nominatim.openstreetmap.org/search';
    const MODAL_ID = 'spoofModalOverlay';
    const TRAVEL_MODE_ICON_SELECTOR = 'i[data-testid="travelModeIcon"]';

    // Default Preset Locations
    const DEFAULT_PRESET_LOCATIONS = [
        { name: 'New York, USA', latitude: 40.7128, longitude: -74.0060 },
        { name: 'London, UK', latitude: 51.5074, longitude: -0.1278 },
        { name: 'Tokyo, Japan', latitude: 35.6762, longitude: 139.6503 },
        { name: 'Sydney, Australia', latitude: -33.8688, longitude: 151.2093 },
        { name: 'Paris, France', latitude: 48.8566, longitude: 2.3522 },
        { name: 'Berlin, Germany', latitude: 52.5200, longitude: 13.4050 },
    ];

    // Default Location Data
    const DEFAULT_LOCATION = {
        enabled: false,
        locationName: '',
        latitude: null,
        longitude: null,
    };

    /**
     * Retrieves stored location data from localStorage.
     * @returns {Object} Stored location data or default if none exists.
     */
    function getStoredLocation() {
        try {
            const data = localStorage.getItem(STORAGE_KEY);
            return data ? JSON.parse(data) : { ...DEFAULT_LOCATION };
        } catch (error) {
            console.error('Error retrieving stored location:', error);
            return { ...DEFAULT_LOCATION };
        }
    }

    /**
     * Saves location data to localStorage.
     * @param {Object} locationData - The location data to store.
     */
    function setStoredLocation(locationData) {
        try {
            localStorage.setItem(STORAGE_KEY, JSON.stringify(locationData));
        } catch (error) {
            console.error('Error setting stored location:', error);
        }
    }

    /**
     * Clears stored location data from localStorage.
     */
    function clearStoredLocation() {
        try {
            localStorage.removeItem(STORAGE_KEY);
        } catch (error) {
            console.error('Error clearing stored location:', error);
        }
    }

    /**
     * Retrieves preset locations from localStorage.
     * @returns {Array} Array of preset location objects.
     */
    function getPresetLocations() {
        try {
            const data = localStorage.getItem(PRESETS_KEY);
            return data ? JSON.parse(data) : [...DEFAULT_PRESET_LOCATIONS];
        } catch (error) {
            console.error('Error retrieving preset locations:', error);
            return [...DEFAULT_PRESET_LOCATIONS];
        }
    }

    /**
     * Saves preset locations to localStorage.
     * @param {Array} presets - Array of preset location objects.
     */
    function setPresetLocations(presets) {
        try {
            localStorage.setItem(PRESETS_KEY, JSON.stringify(presets));
        } catch (error) {
            console.error('Error setting preset locations:', error);
        }
    }

    /**
     * Geocodes a location name to latitude and longitude using Nominatim.
     * @param {string} locationName - The location name to geocode.
     * @returns {Promise<Object>} A promise that resolves to an object containing latitude and longitude.
     */
    async function geocodeLocation(locationName) {
        const params = new URLSearchParams({
            q: locationName,
            format: 'json',
            limit: 1,
        });

        try {
            const response = await fetch(`${GEOCODING_API_URL}?${params.toString()}`, {
                headers: {
                    'User-Agent': 'SniffiesLocationSpoofer/1.0 (+https://yourwebsite.com/)',
                },
            });
            if (!response.ok) {
                throw new Error(`Geocoding API error: ${response.statusText}`);
            }
            const data = await response.json();
            if (data.length === 0) {
                throw new Error('Location not found. Please try a different query.');
            }

            return {
                latitude: parseFloat(data[0].lat),
                longitude: parseFloat(data[0].lon),
            };
        } catch (error) {
            console.error(`Failed to geocode location: ${error.message}`);
            throw new Error('Failed to fetch location. Please try again later.');
        }
    }

    /**
     * Overrides the navigator.geolocation object with spoofed coordinates.
     * @param {Object} spoofedLocation - The spoofed latitude and longitude.
     */
    function overrideGeolocation(spoofedLocation) {
        // Store the original navigator.geolocation if not already stored
        if (!window.__originalGeolocation) {
            window.__originalGeolocation = navigator.geolocation;
        }

        const spoofedGeolocation = {
            getCurrentPosition: function (success, error, options) {
                if (!spoofedLocation.latitude || !spoofedLocation.longitude) {
                    error && error(new Error('Spoofed location is invalid.'));
                    return;
                }
                success && success({
                    coords: {
                        latitude: spoofedLocation.latitude,
                        longitude: spoofedLocation.longitude,
                        accuracy: 100,
                        altitude: null,
                        altitudeAccuracy: null,
                        heading: null,
                        speed: null,
                    },
                    timestamp: Date.now(),
                });
            },
            watchPosition: function (success, error, options) {
                if (!spoofedLocation.latitude || !spoofedLocation.longitude) {
                    error && error(new Error('Spoofed location is invalid.'));
                    return;
                }
                // Return a mock watch ID
                return setInterval(() => {
                    success && success({
                        coords: {
                            latitude: spoofedLocation.latitude,
                            longitude: spoofedLocation.longitude,
                            accuracy: 100,
                            altitude: null,
                            altitudeAccuracy: null,
                            heading: null,
                            speed: null,
                        },
                        timestamp: Date.now(),
                    });
                }, 1000);
            },
            clearWatch: function (id) {
                clearInterval(id);
            },
            // Other geolocation methods can be added if necessary
        };

        // Override the navigator.geolocation
        Object.defineProperty(navigator, 'geolocation', {
            get: () => spoofedGeolocation,
            configurable: true,
        });
    }

    /**
     * Restores the original navigator.geolocation object.
     */
    function restoreOriginalGeolocation() {
        if (window.__originalGeolocation) {
            Object.defineProperty(navigator, 'geolocation', {
                get: () => window.__originalGeolocation,
                configurable: true,
            });
            delete window.__originalGeolocation;
        }
    }

    /**
     * Creates and returns the spoofing modal element with enhanced dark mode styling and customizable presets.
     * @returns {HTMLElement} The modal overlay element.
     */
    function createModal() {
        // Create overlay
        const overlay = document.createElement('div');
        overlay.id = MODAL_ID;
        overlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(11, 17, 31, 0.85); /* Reduced opacity for less darkened background */
            display: none;
            align-items: center;
            justify-content: center;
            z-index: 10000;
        `;

        // Create modal container
        const modal = document.createElement('div');
        modal.style.cssText = `
            background-color: #111d33;
            border-radius: 8px;
            padding: 20px 25px;
            width: 90%;
            max-width: 400px;
            position: relative;
            box-shadow: 0 4px 16px rgba(0,0,0,0.5);
            font-family: Arial, sans-serif;
            color: #ffffff; /* White text for readability */
        `;

        // Close button
        const closeButton = document.createElement('span');
        closeButton.innerHTML = '&times;';
        closeButton.style.cssText = `
            position: absolute;
            top: 15px;
            right: 20px;
            font-size: 24px;
            cursor: pointer;
            color: #ffffff;
            transition: color 0.3s;
        `;
        closeButton.addEventListener('mouseover', () => {
            closeButton.style.color = '#304065';
        });
        closeButton.addEventListener('mouseout', () => {
            closeButton.style.color = '#ffffff';
        });
        closeButton.addEventListener('click', () => {
            overlay.style.display = 'none';
        });

        // Modal title aligned to the left
        const title = document.createElement('h2');
        title.textContent = 'Location Spoofer';
        title.style.cssText = `
            margin: 0;
            margin-bottom: 15px;
            color: #ffffff;
            font-size: 20px;
            text-align: left;
        `;

        // Spoofing Indicator Container
        const spoofingIndicatorContainer = document.createElement('div');
        spoofingIndicatorContainer.id = 'spoofingIndicator';
        spoofingIndicatorContainer.style.cssText = `
            margin-bottom: 15px;
            padding: 10px;
            background-color: #304065;
            border-radius: 10px;
            display: none; /* Hidden by default */
            align-items: center;
            justify-content: space-between;
            cursor: pointer;
            transition: background-color 0.3s;
        `;
        spoofingIndicatorContainer.title = 'Click to disable spoofing';
        spoofingIndicatorContainer.addEventListener('mouseover', () => {
            spoofingIndicatorContainer.style.backgroundColor = '#1e2a4a';
        });
        spoofingIndicatorContainer.addEventListener('mouseout', () => {
            spoofingIndicatorContainer.style.backgroundColor = '#304065';
        });
        spoofingIndicatorContainer.addEventListener('click', () => {
            if (confirm('Do you want to disable location spoofing?')) {
                clearStoredLocation();
                restoreOriginalGeolocation();
                updateSpoofingIndicator();
                alert('Location spoofing has been disabled.');
            }
        });

        const spoofingStatus = document.createElement('span');
        spoofingStatus.textContent = 'Spoofing: On';
        spoofingStatus.style.cssText = `
            font-weight: bold;
        `;

        const spoofingLocation = document.createElement('span');
        spoofingLocation.id = 'spoofingLocation';
        spoofingLocation.textContent = ''; // To be updated dynamically

        spoofingIndicatorContainer.appendChild(spoofingStatus);
        spoofingIndicatorContainer.appendChild(spoofingLocation);

        // Preset Locations Container
        const presetsContainer = document.createElement('div');
        presetsContainer.style.cssText = `
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            justify-content: center;
            margin-bottom: 15px;
        `;

        // Function to create a preset button
        function createPresetButton(preset) {
            const presetButton = document.createElement('button');
            presetButton.textContent = preset.name;
            presetButton.style.cssText = `
                padding: 6px 12px;
                background-color: #304065;
                color: #ffffff;
                border: none;
                border-radius: 20px;
                cursor: pointer;
                font-size: 14px;
                transition: background-color 0.3s;
                display: flex;
                align-items: center;
                gap: 5px;
            `;
            presetButton.addEventListener('mouseover', () => {
                presetButton.style.backgroundColor = '#1e2a4a';
            });
            presetButton.addEventListener('mouseout', () => {
                presetButton.style.backgroundColor = '#304065';
            });
            presetButton.addEventListener('click', () => {
                setStoredLocation({
                    enabled: true,
                    locationName: preset.name,
                    latitude: preset.latitude,
                    longitude: preset.longitude,
                });
                overrideGeolocation({
                    latitude: preset.latitude,
                    longitude: preset.longitude,
                });
                updateSpoofingIndicator();
                alert(`Location spoofed to ${preset.name}.`);
            });

            // Remove button for custom presets only
            if (!DEFAULT_PRESET_LOCATIONS.some(p => p.name === preset.name)) {
                const removeButton = document.createElement('span');
                removeButton.innerHTML = '&times;';
                removeButton.style.cssText = `
                    font-size: 12px;
                    cursor: pointer;
                    color: #ff4d4d;
                    margin-left: 5px;
                `;
                removeButton.addEventListener('click', (e) => {
                    e.stopPropagation();
                    removePreset(preset.name);
                });
                presetButton.appendChild(removeButton);
            }

            return presetButton;
        }

        // Load and display preset buttons
        const presets = getPresetLocations();
        presets.forEach(preset => {
            const presetButton = createPresetButton(preset);
            presetsContainer.appendChild(presetButton);
        });

        // Add Preset Button (with "+" sign)
        const addPresetButton = document.createElement('button');
        addPresetButton.innerHTML = '+';
        addPresetButton.title = 'Add Preset';
        addPresetButton.style.cssText = `
            padding: 6px 12px;
            background-color: #304065;
            color: #ffffff;
            border: none;
            border-radius: 20px;
            cursor: pointer;
            font-size: 16px;
            transition: background-color 0.3s;
            display: flex;
            align-items: center;
            justify-content: center;
            width: 36px;
            height: 36px;
        `;
        addPresetButton.addEventListener('mouseover', () => {
            addPresetButton.style.backgroundColor = '#1e2a4a';
        });
        addPresetButton.addEventListener('mouseout', () => {
            addPresetButton.style.backgroundColor = '#304065';
        });
        addPresetButton.addEventListener('click', () => {
            // Prompt the user to enter a new preset location
            const presetName = prompt('Enter the name of the new preset location:');
            if (presetName) {
                addNewPreset(presetName.trim());
            }
        });
        presetsContainer.appendChild(addPresetButton);

        /**
         * Adds a new preset location.
         * @param {string} presetName - The name of the preset location.
         */
        async function addNewPreset(presetName) {
            if (!presetName) {
                alert('Preset name cannot be empty.');
                return;
            }
            const currentPresets = getPresetLocations();
            // Check for duplicates
            if (currentPresets.some(p => p.name.toLowerCase() === presetName.toLowerCase())) {
                alert('A preset with this name already exists.');
                return;
            }
            try {
                const coords = await geocodeLocation(presetName);
                const newPreset = {
                    name: presetName,
                    latitude: coords.latitude,
                    longitude: coords.longitude,
                };
                currentPresets.push(newPreset);
                setPresetLocations(currentPresets);
                // Add the new preset button
                const newPresetButton = createPresetButton(newPreset);
                // Insert before the addPresetButton
                presetsContainer.insertBefore(newPresetButton, addPresetButton);
                alert(`Preset "${presetName}" added successfully.`);
            } catch (error) {
                alert(error.message);
            }
        }

        /**
         * Removes a preset location by name.
         * @param {string} presetName - The name of the preset to remove.
         */
        function removePreset(presetName) {
            const currentPresets = getPresetLocations();
            const updatedPresets = currentPresets.filter(p => p.name !== presetName);
            setPresetLocations(updatedPresets);
            // Remove the preset button from the modal
            const allPresetButtons = document.querySelectorAll(`#${MODAL_ID} div > button`);
            allPresetButtons.forEach(button => {
                if (button.textContent.startsWith(presetName)) {
                    button.remove();
                }
            });
            alert(`Preset "${presetName}" removed successfully.`);
        }

        // Location Input
        const locationInput = document.createElement('input');
        locationInput.type = 'text';
        locationInput.id = 'locationInput';
        locationInput.placeholder = 'Enter location...';
        locationInput.style.cssText = `
            width: 100%;
            padding: 10px 15px;
            margin: 10px 0;
            border: none;
            border-radius: 4px;
            background-color: #0b111f;
            color: #ffffff;
            font-size: 16px;
        `;
        locationInput.addEventListener('focus', () => {
            locationInput.style.backgroundColor = '#304065';
            locationInput.style.outline = 'none';
        });
        locationInput.addEventListener('blur', () => {
            locationInput.style.backgroundColor = '#0b111f';
        });

        // Action Buttons Container
        const buttonsContainer = document.createElement('div');
        buttonsContainer.style.cssText = `
            display: flex;
            justify-content: space-between;
            flex-wrap: wrap;
            gap: 10px;
            margin-top: 10px;
        `;

        // Save & Apply Button
        const saveButton = document.createElement('button');
        saveButton.textContent = 'Save & Apply';
        saveButton.style.cssText = `
            flex: 1;
            padding: 10px 0;
            background-color: #304065;
            color: #ffffff;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
            transition: background-color 0.3s;
        `;
        saveButton.addEventListener('mouseover', () => {
            saveButton.style.backgroundColor = '#1e2a4a';
        });
        saveButton.addEventListener('mouseout', () => {
            saveButton.style.backgroundColor = '#304065';
        });
        saveButton.addEventListener('click', async () => {
            const locationName = locationInput.value.trim();
            if (!locationName) {
                alert('Please enter a location.');
                return;
            }
            try {
                const coords = await geocodeLocation(locationName);
                setStoredLocation({
                    enabled: true,
                    locationName,
                    latitude: coords.latitude,
                    longitude: coords.longitude,
                });
                overrideGeolocation({
                    latitude: coords.latitude,
                    longitude: coords.longitude,
                });
                updateSpoofingIndicator();
                alert('Location spoofing applied.');
            } catch (error) {
                alert(error.message);
            }
        });

        // Reset Button
        const resetButton = document.createElement('button');
        resetButton.textContent = 'Reset';
        resetButton.style.cssText = `
            flex: 1;
            padding: 10px 0;
            background-color: #dc3545;
            color: #ffffff;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
            transition: background-color 0.3s;
        `;
        resetButton.addEventListener('mouseover', () => {
            resetButton.style.backgroundColor = '#a71d2a';
        });
        resetButton.addEventListener('mouseout', () => {
            resetButton.style.backgroundColor = '#dc3545';
        });
        resetButton.addEventListener('click', () => {
            if (confirm('Are you sure you want to reset to your actual location?')) {
                clearStoredLocation();
                restoreOriginalGeolocation();
                updateSpoofingIndicator();
                alert('Location reset to actual position.');
            }
        });

        // Append buttons to buttons container
        buttonsContainer.appendChild(saveButton);
        buttonsContainer.appendChild(resetButton);

        // Append elements to modal
        modal.appendChild(closeButton);
        modal.appendChild(title);
        modal.appendChild(spoofingIndicatorContainer);
        modal.appendChild(presetsContainer);
        modal.appendChild(locationInput);
        modal.appendChild(buttonsContainer);

        // Append modal to overlay
        overlay.appendChild(modal);

        // Append overlay to body
        document.body.appendChild(overlay);

        return overlay;
    }

    /**
     * Attaches the spoofing modal to the Travel Mode button.
     */
    function attachModalToTravelMode() {
        const overlay = createModal();

        // Function to handle Travel Mode button clicks
        const handleTravelModeClick = (event) => {
            event.preventDefault();
            event.stopPropagation();
            overlay.style.display = 'flex';
        };

        // Wait for the Travel Mode icon to be available in the DOM
        const observer = new MutationObserver((mutations, obs) => {
            const travelModeIcon = document.querySelector(TRAVEL_MODE_ICON_SELECTOR);
            if (travelModeIcon) {
                // Find the closest clickable parent (assuming it's a button or clickable element)
                const travelModeButton = travelModeIcon.closest('button, a, div');
                if (travelModeButton) {
                    // Remove existing event listeners by cloning the node
                    const newTravelModeButton = travelModeButton.cloneNode(true);
                    travelModeButton.parentNode.replaceChild(newTravelModeButton, travelModeButton);

                    // Attach new event listener
                    newTravelModeButton.addEventListener('click', handleTravelModeClick);

                    // Optionally, change the icon to indicate spoofing functionality
                    // Example: Change plane icon to a location pin
                    travelModeIcon.classList.remove('fa-plane');
                    travelModeIcon.classList.add('fa-map-marker-alt'); // Ensure FontAwesome supports this class
                }
                obs.disconnect();
            }
        });

        observer.observe(document.body, { childList: true, subtree: true });
    }

    /**
     * Updates the spoofing indicator within the modal based on spoofing status.
     */
    function updateSpoofingIndicator() {
        const storedLocation = getStoredLocation();
        const indicatorContainer = document.getElementById('spoofingIndicator');
        const spoofingLocation = document.getElementById('spoofingLocation');

        if (storedLocation.enabled && storedLocation.locationName) {
            indicatorContainer.style.display = 'flex';
            spoofingLocation.textContent = `📍 ${storedLocation.locationName}`;
        } else {
            indicatorContainer.style.display = 'none';
            spoofingLocation.textContent = '';
        }
    }

    /**
     * Overrides the Geolocation API if spoofing is enabled.
     */
    function initSpoofing() {
        const storedLocation = getStoredLocation();

        if (storedLocation.enabled && storedLocation.latitude && storedLocation.longitude) {
            overrideGeolocation({
                latitude: storedLocation.latitude,
                longitude: storedLocation.longitude,
            });
            updateSpoofingIndicator();
            console.log(`Location spoofed to: ${storedLocation.locationName} (${storedLocation.latitude}, ${storedLocation.longitude})`);
        } else {
            // Ensure the original Geolocation API is restored
            restoreOriginalGeolocation();
            updateSpoofingIndicator();
        }
    }

    /**
     * Initializes the userscript by setting up the UI and spoofing functionality.
     */
    function init() {
        attachModalToTravelMode();
        initSpoofing();
    }

    // Wait for the page to fully load before initializing
    window.addEventListener('load', init);
})();