// ==UserScript== // @name 全局强制虚拟定位(双重 Hook + 微抖动 + 状态回显) // @namespace http://tampermonkey.net/ // @version 3.0.0 // @description Hook getCurrentPosition + watchPosition,强制返回虚拟坐标;中国视图地图选点 + 状态栏回显 + 星号收藏夹 // @author you // @match *://*/* // @run-at document-start // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_getResourceText // @grant GM_addStyle // @require https://unpkg.com/leaflet@1.9.4/dist/leaflet.js // @require https://cdn.jsdelivr.net/npm/sweetalert2@11 // @resource leafletCSS https://unpkg.com/leaflet@1.9.4/dist/leaflet.css // ==/UserScript== (function () { 'use strict'; // ============================================================ // 第 0 部分:常量 & 存储 // ============================================================ var DEF_LAT = 35.0, DEF_LNG = 105.0; // 默认中国中心 var ACC_BASE = 8; // 精度基准值(米) function getFakeLat() { return GM_getValue('vg_lat', null); } function getFakeLng() { return GM_getValue('vg_lng', null); } function getFakeTs() { return GM_getValue('vg_ts', 0); } var favs = GM_getValue('vg_favs', []); function isVirtual() { return getFakeLat() !== null && getFakeLng() !== null; } // ============================================================ // === 微抖动算法(Anti-Fingerprinting,严格按指定公式) === // ============================================================ function applyJitter(baseLat, baseLng) { var METER_TO_DEG = 1 / 111000; // 1 米 ≈ 0.000009009 度 var MAX_JITTER = 0.2 * METER_TO_DEG; // 半径 0.2 米(手持 GPS 漂移) var jitteredLat = baseLat + (Math.random() - 0.5) * 2 * MAX_JITTER; var jitteredLng = baseLng + (Math.random() - 0.5) * 2 * MAX_JITTER / Math.cos(baseLat * Math.PI / 180); return { lat: jitteredLat, lng: jitteredLng }; } // ============================================================ // === 状态栏 & Logging === // ============================================================ var LOG_STYLE = { init: 'color: cyan;', virtual: 'color: green;', real: 'color: yellow;', fav: 'color: gold;' }; function lg(tag, msg, style) { console.log('%c ' + tag + ' ' + msg, LOG_STYLE[style] || LOG_STYLE.init); } // ============================================================ // === getCurrentPosition Hook === // ============================================================ if (typeof Geolocation !== 'undefined' && Geolocation.prototype) { Geolocation.prototype.getCurrentPosition = function (success, error, options) { var baseLat = getFakeLat() || DEF_LAT; var baseLng = getFakeLng() || DEF_LNG; var j = applyJitter(baseLat, baseLng); var acc = ACC_BASE + (Math.random() * 4 - 2); // 6~10m 随机 lg('[GPS Return]', 'Virtual | Lat: ' + j.lat.toFixed(6) + ', Lng: ' + j.lng.toFixed(6) + ' (Acc: ' + acc.toFixed(1) + 'm)', 'virtual'); var pos = { coords: { latitude: j.lat, longitude: j.lng, accuracy: acc, altitude: null, altitudeAccuracy: null, heading: null, speed: null }, timestamp: Date.now() }; setTimeout(function () { try { success(pos); } catch (e) { console.error('[Virtual GPS] 回调异常', e); } }, 0); }; lg('[GPS Hook]', 'getCurrentPosition initialized', 'init'); } // ============================================================ // === watchPosition Hook === // ============================================================ if (typeof Geolocation !== 'undefined' && Geolocation.prototype) { var _watchIdCounter = 0; var _watchMap = {}; // id → 已返回的位置(保证同一 watcher 返回一致坐标) Geolocation.prototype.watchPosition = function (success, error, options) { var id = ++_watchIdCounter; var baseLat = getFakeLat() || DEF_LAT; var baseLng = getFakeLng() || DEF_LNG; var j = applyJitter(baseLat, baseLng); var acc = ACC_BASE + (Math.random() * 4 - 2); lg('[GPS Return]', 'Virtual (Watch #' + id + ') | Lat: ' + j.lat.toFixed(6) + ', Lng: ' + j.lng.toFixed(6) + ' (Acc: ' + acc.toFixed(1) + 'm)', 'virtual'); var pos = { coords: { latitude: j.lat, longitude: j.lng, accuracy: acc, altitude: null, altitudeAccuracy: null, heading: null, speed: null }, timestamp: Date.now() }; _watchMap[id] = pos; // 保存,模拟同一 watcher 的持续一致性 setTimeout(function () { try { success(pos); } catch (e) { console.error('[Virtual GPS] Watch 回调异常', e); } }, 0); return id; // 返回 watchId,供 clearWatch 使用 }; Geolocation.prototype.clearWatch = function (id) { if (_watchMap[id]) { delete _watchMap[id]; lg('[GPS Hook]', 'Watch #' + id + ' cleared', 'init'); } }; lg('[GPS Hook]', 'watchPosition initialized', 'init'); } // ============================================================ // === 样式注入 === // ============================================================ GM_addStyle(GM_getResourceText('leafletCSS')); GM_addStyle( // 状态栏(40px,常驻,地图顶部) '#vg_status{height:40px;min-height:40px;display:flex;align-items:center;justify-content:space-between;' + 'padding:0 16px;font-family:"PingFang SC","Microsoft YaHei",sans-serif;font-size:12px;font-weight:600;}' + '#vg_status.virtual{background:#4CAF50;color:#fff;}' + '#vg_status.real{background:#FF9800;color:#fff;}' + '#vg_status .src{background:rgba(255,255,255,.25);padding:2px 8px;border-radius:10px;font-size:11px;}' + // 模态框 & 布局 '#vg_overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.55);' + 'z-index:99999;display:flex;align-items:center;justify-content:center;}' + '#vg_modal{background:#fff;border-radius:12px;width:88vw;max-width:1100px;height:82vh;' + 'display:flex;flex-direction:column;overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,.35);}' + '#vg_head{padding:12px 16px;font:700 15px/1.4 "PingFang SC","Microsoft YaHei",sans-serif;' + 'color:#1f2937;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid #e5e7eb;}' + '#vg_close{cursor:pointer;font-size:22px;color:#9ca3af;line-height:1;padding:0 4px;}' + '#vg_close:hover{color:#111827;}' + '#vg_body{flex:1;display:flex;min-height:0;}' + // 收藏夹侧边栏 '#vg_side{width:200px;min-width:200px;border-right:1px solid #e5e7eb;display:flex;flex-direction:column;' + 'font-family:"PingFang SC","Microsoft YaHei",sans-serif;}' + '#vg_side_t{padding:10px 12px;font-size:13px;font-weight:700;color:#374151;border-bottom:1px solid #f3f4f6;}' + '#vg_side_list{flex:1;overflow-y:auto;padding:4px 6px;}' + '.vg_fav{display:flex;justify-content:space-between;align-items:center;padding:8px 6px;' + 'border-bottom:1px solid #f3f4f6;cursor:pointer;border-radius:6px;}' + '.vg_fav:hover{background:#f9fafb;}' + '.vg_fav .nm{flex:1;font-size:13px;color:#1f2937;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}' + '.vg_fav .del{margin-left:6px;cursor:pointer;font-size:12px;opacity:.6;padding:2px;}' + '.vg_fav .del:hover{opacity:1;}' + '.vg_empty{color:#9ca3af;font-size:12px;padding:16px 6px;text-align:center;line-height:1.8;}' + // 地图 '#vg_map{flex:1;min-width:0;height:100%;}' + '#vg_tip{padding:8px;text-align:center;font-size:12px;color:#6b7280;border-top:1px solid #e5e7eb;' + 'font-family:"PingFang SC","Microsoft YaHei",sans-serif;}' + // 星号按钮 '#vg_star{margin-top:6px;background:#fff;border:1px solid #d1d5db;border-radius:8px;' + 'padding:5px 14px;font-size:13px;cursor:pointer;color:#6b7280;transition:.15s;}' + '#vg_star:hover{border-color:#f59e0b;color:#f59e0b;}' + '#vg_star.on{border-color:#f59e0b;color:#f59e0b;background:#fffbeb;}' + '.swal2-container{z-index:100000 !important;}' ); // ============================================================ // === 模态框 + 地图初始化 === // ============================================================ var vgMap = null, vgMarker = null; var selLat = 0, selLng = 0, selFav = null; delete L.Icon.Default.prototype._getIconUrl; L.Icon.Default.mergeOptions({ iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png', iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png', shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png' }); function openModal() { if (document.getElementById('vg_overlay')) return; if (!document.body) { setTimeout(openModal, 100); return; } var overlay = document.createElement('div'); overlay.id = 'vg_overlay'; // ============================================================ // === 状态栏(地图顶部 40px 常驻,避免闪烁) === // ============================================================ var isVirt = isVirtual(); var statusHtml; if (isVirt) { var lat = getFakeLat(), lng = getFakeLng(), ts = getFakeTs(); var dateStr = ts ? new Date(ts).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) : '未知'; statusHtml = '
' + '✅ 虚拟定位已激活 | 📍 (' + lat.toFixed(4) + ', ' + lng.toFixed(4) + ') | 🕒 ' + dateStr + '' + 'SRC: Virtual GPS' + '
'; lg('[GPS Status]', 'Current Mode: VIRTUAL | (' + lat.toFixed(4) + ', ' + lng.toFixed(4) + ')', 'virtual'); } else { statusHtml = '
' + '⚠️ 未设定虚拟位置,当前使用浏览器真实定位' + 'SRC: Browser GPS' + '
'; lg('[GPS Status]', 'Current Mode: REAL', 'real'); } overlay.innerHTML = '
' + statusHtml + '
🗺️ 设置虚拟定位×
' + '
' + '
⭐ 我的收藏
' + '
' + '
' + '
滚轮缩放 / 拖拽平移,点击地图选点;点击左侧收藏可直接飞行到该位置
' + '
'; document.body.appendChild(overlay); document.getElementById('vg_close').addEventListener('click', closeModal); // 收藏夹事件 document.getElementById('vg_side_list').addEventListener('click', function (e) { var del = e.target.closest('.del'); if (del) { e.stopPropagation(); favs.splice(+del.dataset.i, 1); GM_setValue('vg_favs', favs); renderSidebar(); return; } var item = e.target.closest('.vg_fav'); if (item) { selectFromFav(favs[+item.dataset.i]); } }); renderSidebar(); // ============================================================ // === 地图初始化:中国视图 [35.0, 105.0] zoom 4 === // ============================================================ vgMap = L.map('vg_map', { center: [35.0, 105.0], zoom: 4, zoomControl: true, attributionControl: true }); L.tileLayer('https://tile.openstreetmap.de/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap contributors' }).addTo(vgMap); setTimeout(function () { if (vgMap) vgMap.invalidateSize(); }, 200); // ============================================================ // === 状态回显:如果已设定,flyTo 到上次位置 + Marker === // ============================================================ if (isVirt) { var curLat = getFakeLat(), curLng = getFakeLng(); placeMarker(curLat, curLng); vgMap.flyTo([curLat, curLng], 15); } // ============================================================ // === 点击事件:选点 → 确认框(含星号收藏) === // ============================================================ vgMap.on('click', function (e) { selLat = e.latlng.lat; selLng = e.latlng.lng; selFav = null; placeMarker(selLat, selLng); openConfirm(); }); } function placeMarker(lat, lng) { if (vgMarker) { vgMap.removeLayer(vgMarker); } vgMarker = L.marker([lat, lng]).addTo(vgMap); } // === 星号收藏逻辑:从收藏列表 flyTo === function selectFromFav(f) { if (!f || !vgMap) return; selLat = f.lat; selLng = f.lng; selFav = f; placeMarker(f.lat, f.lng); vgMap.flyTo([f.lat, f.lng], 16); setTimeout(openConfirm, 800); } // ============================================================ // === 确认弹窗 + 星号收藏 === // ============================================================ var _confirmBusy = false; function openConfirm() { if (_confirmBusy) return; _confirmBusy = true; var lat = selLat, lng = selLng, fav = selFav; var starred = !!fav; Swal.fire({ title: '是否将虚拟定位设置为该位置?', html: '
' + '纬度: ' + lat.toFixed(6) + '
经度: ' + lng.toFixed(6) + '
' + '' + '
', icon: 'question', showCancelButton: true, confirmButtonText: '确认修改', cancelButtonText: '取消', confirmButtonColor: '#6366f1', didOpen: function () { var starBtn = document.getElementById('vg_star'); if (!starBtn) return; starBtn.addEventListener('click', function () { if (starred) return; starred = true; starBtn.className = 'on'; starBtn.textContent = '★ 已收藏'; var name = prompt('请输入位置名称(如:教学楼A)', '未命名收藏'); if (name === null) { starred = false; starBtn.className = ''; starBtn.textContent = '☆ 收藏此位置'; return; } name = name.trim() || '未命名收藏'; var f = { name: name, lat: lat, lng: lng, ts: Date.now() }; favs.push(f); GM_setValue('vg_favs', favs); selFav = f; fav = f; lg('[Favorite Added]', name, 'fav'); renderSidebar(); }); }, preConfirm: function () { return true; } }).then(function (r) { _confirmBusy = false; if (r.isConfirmed) { GM_setValue('vg_lat', lat); GM_setValue('vg_lng', lng); GM_setValue('vg_ts', Date.now()); lg('[GPS Status]', '已更新虚拟位置 | Lat: ' + lat + ', Lng: ' + lng, 'virtual'); closeModal(); } else { if (vgMarker && vgMap) { vgMap.removeLayer(vgMarker); vgMarker = null; } selFav = null; } }); } // ============================================================ // === 收藏夹渲染 === // ============================================================ function renderSidebar() { var el = document.getElementById('vg_side_list'); if (!el) return; if (!favs.length) { el.innerHTML = '
暂无收藏
点击星号添加
'; return; } el.innerHTML = favs.map(function (f, i) { return '
' + '★ ' + f.name + '' + '🗑️' + '
'; }).join(''); } function closeModal() { var overlay = document.getElementById('vg_overlay'); try { if (vgMap) { vgMap.remove(); } } catch (e) { console.warn('[Virtual GPS] 地图销毁异常(已忽略)', e); } vgMap = null; vgMarker = null; if (overlay) { overlay.remove(); } } // ============================================================ // === 注册菜单命令 === // ============================================================ GM_registerMenuCommand('设置虚拟定位', openModal); lg('[GPS Hook]', 'Initialized (getCurrentPosition + watchPosition)', 'init'); })();