虚拟定位的意义
在社交应用、照片分享或其他基于位置的服务中,使用虚拟定位可以避免暴露真实的居住地、工作地点或常去场所,降低被恶意追踪或暴露行踪的风险。
代码实现
// ==UserScript==
// @name 虚拟定位
// @namespace https://allok.dev/
// @version 1.0.0
// @description 拦截并修改地理位置信息,保护隐私
// @author Outsider
// @match *://*/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// 默认虚假位置配置
const DEFAULT_LOCATION = {
latitude: 39.9042,
longitude: 116.4074,
accuracy: 10 //精度
};
// 从URL参数获取位置配置
function getLocationFromURL() {
const urlParams = new URLSearchParams(window.location.search);
// 检查是否有经纬度参数
const latitude = parseFloat(urlParams.get('latitude'));
const longitude = parseFloat(urlParams.get('longitude'));
const accuracy = parseFloat(urlParams.get('accuracy')) || DEFAULT_LOCATION.accuracy;
// 如果有有效的经纬度参数,直接使用
if (!isNaN(latitude) && !isNaN(longitude)) {
return {
latitude: latitude,
longitude: longitude,
accuracy: accuracy
};
}
// 检查是否明确启用location-guard
const isEnabled = urlParams.get('location-guard') === 'true';
if (isEnabled) {
return DEFAULT_LOCATION;
}
return null;
}
// 检查是否应该启用位置保护
function shouldProtectLocation() {
// 检查URL参数
const urlLocation = getLocationFromURL();
if (urlLocation) {
return urlLocation;
}
// 检查是否通过URL参数明确禁用
const urlParams = new URLSearchParams(window.location.search);
const isDisabled = urlParams.get('location-guard') === 'false';
if (isDisabled) {
return null;
}
// 默认启用位置保护,使用默认位置
return DEFAULT_LOCATION;
}
// 创建虚假的Position对象
function createFakePosition(location) {
return {
coords: {
latitude: location.latitude,
longitude: location.longitude,
altitude: null,
accuracy: location.accuracy,
altitudeAccuracy: null,
heading: null,
speed: null
},
timestamp: Date.now()
};
}
// 拦截地理位置API
function interceptGeolocation() {
const fakeLocation = shouldProtectLocation();
if (!fakeLocation) {
// 不需要保护,保持原始行为
return;
}
// 静默运行,不输出日志
// 保存原始的geolocation对象
const originalGeolocation = navigator.geolocation;
// 创建虚假的geolocation对象
const fakeGeolocation = {
getCurrentPosition: function(successCallback, errorCallback, options) {
// 拦截 getCurrentPosition 调用
if (typeof successCallback === 'function') {
// 模拟异步调用
setTimeout(() => {
const fakePosition = createFakePosition(fakeLocation);
successCallback(fakePosition);
}, Math.random() * 100 + 50); // 随机延迟50-150ms,更真实
}
},
watchPosition: function(successCallback, errorCallback, options) {
// 拦截 watchPosition 调用
if (typeof successCallback === 'function') {
// 立即返回虚假位置
setTimeout(() => {
const fakePosition = createFakePosition(fakeLocation);
successCallback(fakePosition);
}, Math.random() * 100 + 50);
}
// 返回一个虚假的watchId
return Math.floor(Math.random() * 1000000);
},
clearWatch: function(watchId) {
// 拦截 clearWatch 调用
// 什么都不做,因为我们没有真正的watch
}
};
// 多重拦截策略确保生效
try {
// 方法1: 直接替换navigator.geolocation
Object.defineProperty(navigator, 'geolocation', {
value: fakeGeolocation,
writable: false,
configurable: false
});
} catch (e) {
// 无法直接替换navigator.geolocation
}
// 方法2: 拦截geolocation的方法
if (navigator.geolocation) {
try {
navigator.geolocation.getCurrentPosition = fakeGeolocation.getCurrentPosition;
navigator.geolocation.watchPosition = fakeGeolocation.watchPosition;
navigator.geolocation.clearWatch = fakeGeolocation.clearWatch;
} catch (e) {
// 无法替换geolocation方法
}
}
// 方法3: 拦截可能的权限API
if (navigator.permissions && navigator.permissions.query) {
const originalQuery = navigator.permissions.query;
navigator.permissions.query = function(permissionDesc) {
if (permissionDesc.name === 'geolocation') {
// 拦截权限查询
return Promise.resolve({ state: 'granted' });
}
return originalQuery.call(this, permissionDesc);
};
}
}
// 在页面加载前拦截
interceptGeolocation();
})();
注意事项
- 此脚本需要在页面加载前运行(@run-at document-start)
- URL参数中的经纬度具有最高优先级
- 可以根据需要修改DEFAULT_LOCATION常量来更改默认位置
- 脚本完全静默运行,不会产生任何输出或提示
- URL参数使用方式:
?latitude=39.9042&longitude=116.4074&accuracy=5
,注意前端Hash路由使用方式:https://example.dev/?latitude=39.9042&longitude=116.4074&accuracy=5#/dev
- URL参数有
location-guard=false
时不开启虚拟定位