您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Integrate location spoofing directly into Sniffies' Travel Mode UI (city search, draggable pin).
当前为
// ==UserScript== // @name Sniffies Native Location Spoofer (Integrated) // @namespace https://sniffies.com/ // @version 2.0 // @description Integrate location spoofing directly into Sniffies' Travel Mode UI (city search, draggable pin). // @author Your Name // @match https://sniffies.com/* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // Configuration const STORAGE_KEY = 'sniffiesLocationSpoofer'; const GEOCODING_API_URL = 'https://nominatim.openstreetmap.org/search'; const TRAVEL_MODE_INPUT_SELECTOR = 'input[data-testid="travel-mode-location-input"]'; const MAP_PIN_SELECTOR = '.map-pin-selector'; const TRAVEL_MODE_BUTTON_SELECTOR = '.lower-map-icon.travel-on-map'; // Utility Functions function getStoredLocation() { const data = localStorage.getItem(STORAGE_KEY); return data ? JSON.parse(data) : { enabled: false, locationName: '', latitude: 37.7749, longitude: -122.4194 }; } function setStoredLocation(locationData) { localStorage.setItem(STORAGE_KEY, JSON.stringify(locationData)); } async function geocodeLocation(locationName) { const params = new URLSearchParams({ q: locationName, format: 'json', limit: 1 }); const response = await fetch(`${GEOCODING_API_URL}?${params.toString()}`, { method: 'GET', headers: { 'Accept': 'application/json' } }); 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) }; } function overrideGeolocation(spoofedLocation) { const originalGeolocation = navigator.geolocation; const spoofedGeolocation = { getCurrentPosition: function (success, error, options) { success({ coords: { latitude: spoofedLocation.latitude, longitude: spoofedLocation.longitude, altitude: null, accuracy: 100, altitudeAccuracy: null, heading: null, speed: null }, timestamp: Date.now() }); }, watchPosition: function (success, error, options) { return originalGeolocation.watchPosition.call(navigator.geolocation, success, error, options); }, clearWatch: function (id) { originalGeolocation.clearWatch.call(navigator.geolocation, id); } }; Object.defineProperty(navigator, 'geolocation', { get: function () { return spoofedGeolocation; } }); } function applySpoofIfEnabled() { const stored = getStoredLocation(); if (stored.enabled) { overrideGeolocation({ latitude: stored.latitude, longitude: stored.longitude }); } } // Attach a handler to the Travel Mode city search input function attachSearchInputHandler(input) { let lastQuery = ''; input.addEventListener('change', async () => { const query = input.value.trim(); if (query && query !== lastQuery) { lastQuery = query; try { const coords = await geocodeLocation(query); const stored = getStoredLocation(); stored.enabled = true; stored.locationName = query; stored.latitude = coords.latitude; stored.longitude = coords.longitude; setStoredLocation(stored); overrideGeolocation({ latitude: coords.latitude, longitude: coords.longitude }); } catch (e) { console.error('Failed to geocode:', e.message); } } }); } // Observe when the Travel Mode UI is available, then attach handlers function waitForTravelModeUI() { const observer = new MutationObserver((mutations, obs) => { const searchInput = document.querySelector(TRAVEL_MODE_INPUT_SELECTOR); if (searchInput) { obs.disconnect(); attachSearchInputHandler(searchInput); } }); observer.observe(document.body, { childList: true, subtree: true }); } // Watch the draggable pin for updates in location function watchPinPosition() { const pinObserver = new MutationObserver(() => { const pinElement = document.querySelector(MAP_PIN_SELECTOR); if (pinElement) { const lat = parseFloat(pinElement.getAttribute('data-lat')); const lng = parseFloat(pinElement.getAttribute('data-lng')); if (!isNaN(lat) && !isNaN(lng)) { const stored = getStoredLocation(); stored.enabled = true; stored.latitude = lat; stored.longitude = lng; setStoredLocation(stored); overrideGeolocation({ latitude: lat, longitude: lng }); } } }); // Observe attribute changes in entire body, filter in callback pinObserver.observe(document.body, { attributes: true, subtree: true, childList: true }); } // Optional: Add a toggle button next to Travel Mode to enable/disable spoofing function addSpoofToggle() { const checkButtonInterval = setInterval(() => { const travelModeButton = document.querySelector(TRAVEL_MODE_BUTTON_SELECTOR); if (travelModeButton) { clearInterval(checkButtonInterval); // Create a toggle button const btn = document.createElement('button'); btn.textContent = 'Spoof: OFF'; btn.style.marginLeft = '10px'; btn.style.cursor = 'pointer'; btn.style.padding = '5px'; btn.style.fontSize = '14px'; btn.style.background = '#007bff'; btn.style.color = '#fff'; btn.style.border = 'none'; btn.style.borderRadius = '4px'; const updateButtonState = () => { const stored = getStoredLocation(); btn.textContent = `Spoof: ${stored.enabled ? 'ON' : 'OFF'}`; btn.style.background = stored.enabled ? '#28a745' : '#007bff'; }; btn.addEventListener('click', () => { const stored = getStoredLocation(); stored.enabled = !stored.enabled; setStoredLocation(stored); if (!stored.enabled) { // Reload to restore real geolocation window.location.reload(); } else { // Re-apply the override overrideGeolocation({ latitude: stored.latitude, longitude: stored.longitude }); } updateButtonState(); }); travelModeButton.parentNode.appendChild(btn); updateButtonState(); } }, 1000); } function init() { applySpoofIfEnabled(); waitForTravelModeUI(); watchPinPosition(); addSpoofToggle(); } window.addEventListener('load', init); })();