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