您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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 that shows real location when spoofing is deactivated.
// ==UserScript== // @name Sniffies Integrated Location Spoofer // @namespace https://sniffies.com/ // @version 3.6 // @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 that shows real location when spoofing is deactivated. // @author Your Name // @match https://sniffies.com/* // @grant none // @license MIT // @run-at document-start // ==/UserScript== (function () { 'use strict'; // Constants const STORAGE_KEY = 'sniffiesLocationSpoofer'; const PRESETS_KEY = 'sniffiesLocationPresets'; const GEOCODING_API_URL = 'https://nominatim.openstreetmap.org/search'; const REVERSE_GEOCODING_API_URL = 'https://nominatim.openstreetmap.org/reverse'; const MODAL_ID = 'spoofModalOverlay'; const TRAVEL_MODE_ICON_SELECTOR = 'i[data-testid="travelModeIcon"]'; const INDICATOR_ID = 'locationSpoofingIndicator'; // New ID for the indicator // 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, }; // Global variables let activeWatchId = null; let originalTravelModeButton = null; let originalTravelModeIconClasses = []; let isUserscriptDisabled = false; /** * 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.'); } } /** * Reverse geocodes latitude and longitude to a human-readable location name using Nominatim. * @param {number} latitude - The latitude to reverse geocode. * @param {number} longitude - The longitude to reverse geocode. * @returns {Promise<string>} A promise that resolves to the location name. */ async function reverseGeocodeLocation(latitude, longitude) { const params = new URLSearchParams({ lat: latitude, lon: longitude, format: 'json', }); try { const response = await fetch(`${REVERSE_GEOCODING_API_URL}?${params.toString()}`, { headers: { 'User-Agent': 'SniffiesLocationSpoofer/1.0 (+https://yourwebsite.com/)', }, }); if (!response.ok) { throw new Error(`Reverse geocoding API error: ${response.statusText}`); } const data = await response.json(); return data.display_name || 'Unknown Location'; } catch (error) { console.error(`Failed to reverse geocode location: ${error.message}`); return 'Unknown Location'; } } /** * 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 and store the interval activeWatchId = setInterval(() => { success && success({ coords: { latitude: spoofedLocation.latitude, longitude: spoofedLocation.longitude, accuracy: 100, altitude: null, altitudeAccuracy: null, heading: null, speed: null, }, timestamp: Date.now(), }); }, 1000); return activeWatchId; }, clearWatch: function (id) { if (id === activeWatchId) { clearInterval(id); activeWatchId = null; } }, // 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', { value: window.__originalGeolocation, writable: true, configurable: true, }); delete window.__originalGeolocation; } } /** * Reverts the Travel Mode button and icon to their original state. */ function restoreOriginalTravelModeButton() { if (originalTravelModeButton) { const currentTravelModeIcon = originalTravelModeButton.querySelector(TRAVEL_MODE_ICON_SELECTOR); if (currentTravelModeIcon) { // Remove spoofed classes currentTravelModeIcon.className = ''; // Restore original classes originalTravelModeIconClasses.forEach(cls => currentTravelModeIcon.classList.add(cls)); } // Remove the custom event listener by cloning the node again const newTravelModeButton = originalTravelModeButton.cloneNode(true); originalTravelModeButton.parentNode.replaceChild(newTravelModeButton, originalTravelModeButton); originalTravelModeButton = null; originalTravelModeIconClasses = []; } } /** * Creates and returns the spoofing modal element with enhanced dark mode styling and customizable presets. * Implements responsive design for dynamic sizing and formatting. * @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; overflow: auto; /* Allow scrolling on smaller screens */ `; // Create modal container const modal = document.createElement('div'); modal.style.cssText = ` background-color: #111d33; border-radius: 8px; padding: 20px 25px; width: 90%; max-width: 500px; /* Base max-width */ position: relative; box-shadow: 0 4px 16px rgba(0,0,0,0.5); font-family: Arial, sans-serif; color: #ffffff; /* White text for readability */ display: flex; flex-direction: column; box-sizing: border-box; `; // Responsive adjustments using media queries const responsiveStyles = document.createElement('style'); responsiveStyles.textContent = ` @media (max-width: 768px) { #${MODAL_ID} div { padding: 15px 20px; } #${MODAL_ID} h2 { font-size: 18px; } #${MODAL_ID} input { font-size: 14px; padding: 8px 12px; } #${MODAL_ID} button { font-size: 14px; padding: 8px 0; } } @media (max-width: 480px) { #${MODAL_ID} div { padding: 10px 15px; } #${MODAL_ID} h2 { font-size: 16px; } #${MODAL_ID} input { font-size: 12px; padding: 6px 10px; } #${MODAL_ID} button { font-size: 12px; padding: 6px 0; } #${MODAL_ID} button { flex: 100%; } #${MODAL_ID} .buttonsContainer { flex-direction: column; } } `; document.head.appendChild(responsiveStyles); // Close button const closeButton = document.createElement('span'); closeButton.innerHTML = '×'; 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'; // Ensure the addPresetButton is visible if it was replaced showAddPresetButton(); }); // 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 (Redesigned) const spoofingIndicatorContainer = document.createElement('div'); spoofingIndicatorContainer.id = INDICATOR_ID; // Updated ID spoofingIndicatorContainer.style.cssText = ` margin-bottom: 20px; padding: 10px 15px; background-color: #1e2a4a; border-radius: 4px; display: flex; align-items: center; justify-content: space-between; cursor: pointer; transition: background-color 0.3s; flex-wrap: wrap; `; spoofingIndicatorContainer.title = 'Click to toggle spoofing'; const indicatorStatus = document.createElement('span'); indicatorStatus.id = 'indicatorStatusText'; indicatorStatus.style.cssText = ` font-weight: bold; font-size: 16px; `; indicatorStatus.textContent = 'Disabled'; // Default text // **Added: Location Info Span** const locationInfo = document.createElement('span'); locationInfo.id = 'locationInfoText'; locationInfo.style.cssText = ` font-size: 16px; `; // Initially empty; will be populated when spoofing is enabled locationInfo.textContent = ''; spoofingIndicatorContainer.appendChild(indicatorStatus); spoofingIndicatorContainer.appendChild(locationInfo); // Append the new span // Event listener to toggle spoofing on indicator click spoofingIndicatorContainer.addEventListener('click', () => { const storedLocation = getStoredLocation(); if (storedLocation.enabled) { resetSpoofing(); window.location.reload(); // Refresh to apply changes } else { overlay.style.display = 'flex'; } }); // Preset Locations Container const presetsContainer = document.createElement('div'); presetsContainer.style.cssText = ` display: flex; flex-wrap: wrap; gap: 10px; justify-content: flex-start; margin-bottom: 15px; /* Removed position: relative; */ `; // Trash Icon const trashIcon = document.createElement('button'); trashIcon.innerHTML = '🗑️'; // Unicode trash can icon trashIcon.title = 'Delete Presets'; trashIcon.style.cssText = ` padding: 6px 12px; background-color: #dc3545; color: #ffffff; border: none; border-radius: 20px; cursor: pointer; font-size: 16px; transition: background-color 0.3s; display: none; /* Hidden by default */ align-items: center; justify-content: center; width: 36px; height: 36px; `; trashIcon.addEventListener('mouseover', () => { trashIcon.style.backgroundColor = '#a71d2a'; }); trashIcon.addEventListener('mouseout', () => { trashIcon.style.backgroundColor = '#dc3545'; }); // Make trashIcon a drop target trashIcon.addEventListener('dragover', (e) => { e.preventDefault(); trashIcon.style.backgroundColor = '#ff1a1a'; }); trashIcon.addEventListener('dragleave', () => { trashIcon.style.backgroundColor = '#dc3545'; }); trashIcon.addEventListener('drop', (e) => { e.preventDefault(); const presetName = e.dataTransfer.getData('text/plain'); if (presetName) { removePreset(presetName); trashIcon.style.backgroundColor = '#dc3545'; // Hide the trash icon after dropping trashIcon.style.display = 'none'; // Show the addPresetButton again showAddPresetButton(); } }); // Function to show the addPresetButton function showAddPresetButton() { addPresetButton.style.display = 'flex'; trashIcon.style.display = 'none'; } // Function to show the trashIcon function showTrashIcon() { addPresetButton.style.display = 'none'; trashIcon.style.display = 'flex'; } // Function to handle drag start function handleDragStart(e, presetName) { e.dataTransfer.setData('text/plain', presetName); // Show the trash icon showTrashIcon(); } // Function to handle drag end function handleDragEnd() { // Hide the trash icon trashIcon.style.display = 'none'; // Show the addPresetButton again showAddPresetButton(); } // 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); // Append the trashIcon to presetsContainer (after the last preset) presetsContainer.appendChild(trashIcon); /** * Adds a new preset location. * @param {string} presetName - The name of the preset location. */ async function addNewPreset(presetName) { if (!presetName) { // Removed alert return; } const currentPresets = getPresetLocations(); // Check for duplicates if (currentPresets.some(p => p.name.toLowerCase() === presetName.toLowerCase())) { // Removed alert 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); // Optionally, log success instead of alert console.log(`Preset "${presetName}" added successfully.`); } catch (error) { console.error(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 = presetsContainer.querySelectorAll('button'); allPresetButtons.forEach(button => { if (button.textContent.startsWith(presetName)) { button.remove(); } }); // Optionally, log removal instead of alert console.log(`Preset "${presetName}" removed successfully.`); } /** * Function to create a preset button with draggable functionality. * @param {Object} preset - The preset location object. * @returns {HTMLElement} The created preset button element. */ 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.setAttribute('draggable', 'true'); // Make the button draggable 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(); // Removed alert console.log(`Location spoofed to ${preset.name}.`); // Refresh the page after saving and applying spoofing window.location.reload(); // **Added to refresh the page** }); // Add event listeners for drag events presetButton.addEventListener('dragstart', (e) => handleDragStart(e, preset.name)); presetButton.addEventListener('dragend', handleDragEnd); return presetButton; } // Load and display preset buttons const presets = getPresetLocations(); presets.forEach(preset => { const presetButton = createPresetButton(preset); presetsContainer.insertBefore(presetButton, addPresetButton); }); // Location Input const locationInput = document.createElement('input'); locationInput.type = 'text'; locationInput.id = 'locationInput'; // **Changed placeholder text as per user request** locationInput.placeholder = 'Where to?'; // **Updated placeholder** locationInput.style.cssText = ` width: 100%; padding: 10px 15px; margin: 10px 0; border: none; border-radius: 4px; background-color: #0b111f; color: #ffffff; font-size: 16px; box-sizing: border-box; `; 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.className = 'buttonsContainer'; // Added class for media query buttonsContainer.style.cssText = ` display: flex; justify-content: space-between; flex-wrap: wrap; gap: 10px; margin-top: 10px; `; // Apply Button (formerly "Save & Apply") const applyButton = document.createElement('button'); applyButton.textContent = 'Apply'; // **Changed button text to "Apply"** applyButton.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; min-width: 100px; `; applyButton.addEventListener('mouseover', () => { applyButton.style.backgroundColor = '#1e2a4a'; }); applyButton.addEventListener('mouseout', () => { applyButton.style.backgroundColor = '#304065'; }); applyButton.addEventListener('click', async () => { const locationName = locationInput.value.trim(); if (!locationName) { // Removed alert 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(); // Removed alert console.log('Location spoofing applied.'); // **Added to refresh the page after saving and applying spoofing** window.location.reload(); // **Ensures page refreshes upon clicking "Apply"** } catch (error) { console.error(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; min-width: 100px; `; resetButton.addEventListener('mouseover', () => { resetButton.style.backgroundColor = '#a71d2a'; }); resetButton.addEventListener('mouseout', () => { resetButton.style.backgroundColor = '#dc3545'; }); resetButton.addEventListener('click', () => { // Trigger the reset functionality without confirmation resetSpoofing(); // **Added to refresh the page after resetting spoofing** window.location.reload(); // **Ensures page refreshes upon clicking "Reset"** }); // Append buttons to buttons container buttonsContainer.appendChild(applyButton); // **Changed from saveButton to applyButton** 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); // **Added: Dismiss modal when clicking outside the modal box** overlay.addEventListener('click', (e) => { if (e.target === overlay) { // Ensure the click is on the overlay, not the modal overlay.style.display = 'none'; // Ensure the addPresetButton is visible if it was replaced showAddPresetButton(); } }); 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) { // Store references to original elements and classes originalTravelModeButton = travelModeButton; originalTravelModeIconClasses = [...travelModeIcon.classList]; // 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. * Shows spoofed location if enabled, otherwise shows real location. */ async function updateSpoofingIndicator() { const storedLocation = getStoredLocation(); const indicatorContainer = document.getElementById(INDICATOR_ID); const statusText = document.getElementById('indicatorStatusText'); const locationInfo = document.getElementById('locationInfoText'); // Access the new span if (storedLocation.enabled && storedLocation.locationName) { // Spoofing is active indicatorContainer.style.backgroundColor = '#1e2a4a'; indicatorContainer.style.borderLeft = '5px solid #28a745'; // Green border statusText.textContent = 'Enabled'; statusText.style.color = '#28a745'; // Green text // Display spoofed location with location pin emoji on the left locationInfo.textContent = `📍 ${storedLocation.locationName}`; } else { // Spoofing is inactive; show actual location indicatorContainer.style.backgroundColor = '#1e2a4a'; indicatorContainer.style.borderLeft = '5px solid #dc3545'; // Red border statusText.textContent = 'Disabled'; statusText.style.color = '#dc3545'; // Red text // Clear the locationInfo span locationInfo.textContent = ''; // Fetch and display the real location try { const position = await getRealPosition(); const { latitude, longitude } = position.coords; const locationName = await reverseGeocodeLocation(latitude, longitude); // Optionally, you can display the real location if desired // For now, we're keeping the indicator as 'Disabled' } catch (error) { statusText.textContent = 'Disabled'; statusText.style.color = '#dc3545'; // Red text console.error(error.message); } } } /** * Gets the real geolocation of the user. * @returns {Promise<GeolocationPosition>} Promise resolving to the user's real geolocation. */ function getRealPosition() { return new Promise((resolve, reject) => { navigator.geolocation.getCurrentPosition(resolve, reject, { enableHighAccuracy: true, timeout: 10000, maximumAge: 0, }); }); } /** * Resets the spoofing by restoring original geolocation and removing all spoofing effects. */ function resetSpoofing() { // Set the disabled flag isUserscriptDisabled = true; // Clear stored location data clearStoredLocation(); // Restore the original geolocation restoreOriginalGeolocation(); // Clear any active watch intervals if (activeWatchId) { navigator.geolocation.clearWatch(activeWatchId); } // Restore the original Travel Mode button and icon restoreOriginalTravelModeButton(); // Update spoofing indicator updateSpoofingIndicator(); // Hide the modal if it's open const overlay = document.getElementById(MODAL_ID); if (overlay) { overlay.style.display = 'none'; } // Removed alert console.log('Location reset to actual position. The userscript has been disabled.'); } /** * Overrides the Geolocation API if spoofing is enabled and the userscript is not disabled. */ function initSpoofing() { if (isUserscriptDisabled) return; 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() { if (isUserscriptDisabled) return; attachModalToTravelMode(); initSpoofing(); } // Wait for the DOM to be fully loaded before initializing window.addEventListener('DOMContentLoaded', init); })();