// ==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);
})();