您需要先安装一个扩展,例如 篡改猴、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.
当前为
// ==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 = '×'; 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 = '×'; 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); })();