// ==UserScript== // @name 广建实习定位助手 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 实习系统定位注入,支持地图选点和坐标缓存 // @author atvkh // @match *://jw.gdcvi.edu.cn/sx/ZhuJMB010306/ByJwb/* // @run-at document-start // @grant GM_getValue // @grant GM_setValue // ==/UserScript== (function() { 'use strict'; // ========== 默认坐标(GCJ02)========== const DEFAULT_LAT = 23.1291; const DEFAULT_LNG = 113.2644; // ========== 存储 ========== const Store = { get: (k, d) => { try { return typeof GM_getValue === 'function' ? GM_getValue(k, d) : (parseFloat(localStorage.getItem(k)) || d); } catch(e) { return d; } }, set: (k, v) => { try { if (typeof GM_setValue === 'function') GM_setValue(k, v); else localStorage.setItem(k, v); } catch(e) {} }, getJSON: (k, d) => { try { let v = typeof GM_getValue === 'function' ? GM_getValue(k, null) : localStorage.getItem(k); if (!v) return d; return typeof v === 'string' ? JSON.parse(v) : v; } catch(e) { return d; } }, setJSON: (k, v) => { try { let s = JSON.stringify(v); if (typeof GM_setValue === 'function') GM_setValue(k, s); else localStorage.setItem(k, s); } catch(e) {} } }; let F_LAT = Store.get('sx_fake_lat', DEFAULT_LAT); let F_LNG = Store.get('sx_fake_lng', DEFAULT_LNG); // ========== iframe 坐标同步 ========== if (window === window.top) { setInterval(() => { document.querySelectorAll('iframe').forEach(f => { try { if (f.contentWindow) f.contentWindow.postMessage({ type: 'SYNC_COORD', lat: F_LAT, lng: F_LNG }, '*'); } catch(err){} }); }, 1000); } // ========== 主世界注入脚本 ========== const mainWorldScript = function(initLat, initLng) { if (window.__SX_HOOK_ACTIVE__) return; window.__SX_HOOK_ACTIVE__ = true; let lat = parseFloat(initLat); let lng = parseFloat(initLng); window.addEventListener('message', (e) => { if (e.data && e.data.type === 'SYNC_COORD') { lat = parseFloat(e.data.lat); lng = parseFloat(e.data.lng); } }); // ----- 坐标转换 GCJ02 <-> WGS84 ----- const PI = 3.1415926535897932384626; const a = 6378245.0; const ee = 0.00669342162296594323; const transformLat = (x, y) => { let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0; ret += (20.0 * Math.sin(y * PI) + 40.0 * Math.sin(y / 3.0 * PI)) * 2.0 / 3.0; ret += (160.0 * Math.sin(y / 12.0 * PI) + 320 * Math.sin(y * PI / 30.0)) * 2.0 / 3.0; return ret; }; const transformLng = (x, y) => { let ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0; ret += (20.0 * Math.sin(x * PI) + 40.0 * Math.sin(x / 3.0 * PI)) * 2.0 / 3.0; ret += (150.0 * Math.sin(x / 12.0 * PI) + 300.0 * Math.sin(x / 30.0 * PI)) * 2.0 / 3.0; return ret; }; const gcj02towgs84 = (lng, lat) => { let dlat = transformLat(lng - 105.0, lat - 35.0); let dlng = transformLng(lng - 105.0, lat - 35.0); let radlat = lat / 180.0 * PI; let magic = Math.sin(radlat); magic = 1 - ee * magic * magic; let sqrtmagic = Math.sqrt(magic); dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI); dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI); return [lng * 2 - (lng + dlng), lat * 2 - (lat + dlat)]; }; const getFakeData = () => { const jitter = () => (Math.random() - 0.5) * 0.00004; return { lat: lat + jitter(), lng: lng + jitter(), accuracy: 10 + Math.random() * 5 }; }; // ----- Function.toString 伪装 ----- const origToString = Function.prototype.toString; Object.defineProperty(Function.prototype, 'toString', { value: new Proxy(origToString, { apply(target, thisArg, args) { if (thisArg && thisArg.__hook_name) return 'function ' + thisArg.__hook_name + '() { [native code] }'; return Reflect.apply(target, thisArg, args); } }), configurable: true, writable: true }); // ----- 拦截 iframe src 防止绕过 ----- try { const desc = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'src'); if (desc) { const newSet = new Proxy(desc.set, { apply(target, thisArg, args) { if (args[0] && typeof args[0] === 'string' && args[0].includes('geolocation')) args[0] = 'about:blank'; return Reflect.apply(target, thisArg, args); } }); newSet.__hook_name = 'set src'; Object.defineProperty(HTMLIFrameElement.prototype, 'src', { set: newSet, get: desc.get, configurable: desc.configurable, enumerable: desc.enumerable }); } } catch(e) {} // ----- Hook W3C Geolocation ----- const hookW3C = (origMethod, name) => { if (typeof origMethod !== 'function') return origMethod; const proxy = new Proxy(origMethod, { apply(target, thisArg, args) { const suc = args[0]; if (typeof suc === 'function') { const fd = getFakeData(); const [wgsLng, wgsLat] = gcj02towgs84(fd.lng, fd.lat); setTimeout(() => suc({ coords: { latitude: wgsLat, longitude: wgsLng, accuracy: fd.accuracy, altitude: null, altitudeAccuracy: null, heading: null, speed: null }, timestamp: Date.now() }), 50); } return name === 'watchPosition' ? Math.floor(Math.random() * 10000) : undefined; } }); proxy.__hook_name = name; return proxy; }; if (window.Geolocation && Geolocation.prototype) { try { Object.defineProperty(Geolocation.prototype, 'getCurrentPosition', { value: hookW3C(Geolocation.prototype.getCurrentPosition, 'getCurrentPosition'), configurable: false, writable: false }); Object.defineProperty(Geolocation.prototype, 'watchPosition', { value: hookW3C(Geolocation.prototype.watchPosition, 'watchPosition'), configurable: false, writable: false }); } catch(e) {} } if (navigator.geolocation) { try { const geoProxy = new Proxy(navigator.geolocation, { get(target, prop) { if (prop === 'getCurrentPosition') return hookW3C(target[prop], 'getCurrentPosition'); if (prop === 'watchPosition') return hookW3C(target[prop], 'watchPosition'); return typeof target[prop] === 'function' ? target[prop].bind(target) : target[prop]; } }); Object.defineProperty(navigator, 'geolocation', { value: geoProxy, configurable: false, writable: false, enumerable: true }); } catch(e) {} } // ----- Hook 腾讯地图 Geolocation ----- const proxyCache = new WeakSet(); const hookTencentGeo = (OrigGeo) => { if (proxyCache.has(OrigGeo)) return OrigGeo; const HookedGeo = new Proxy(OrigGeo, { construct(target, args) { const instance = Reflect.construct(target, args); if (proxyCache.has(instance)) return instance; const InstanceProxy = new Proxy(instance, { get(target2, prop) { if (prop === 'getLocation' || prop === 'getIpLocation') { const fakeMethod = function(suc) { if (suc) { const fd = getFakeData(); setTimeout(() => suc({ module: 'geolocation', type: 'h5', lat: fd.lat, lng: fd.lng, accuracy: fd.accuracy, nation: '中国', province: '广东省', city: '广州市', adcode: '440100', __fake__: true }), 50); } }; fakeMethod.__hook_name = prop; return fakeMethod; } const val = Reflect.get(target2, prop); return typeof val === 'function' ? val.bind(target2) : val; } }); proxyCache.add(InstanceProxy); return InstanceProxy; } }); proxyCache.add(HookedGeo); return HookedGeo; }; let _qq = window.qq; Object.defineProperty(window, 'qq', { get() { if (_qq && !proxyCache.has(_qq)) { _qq = new Proxy(_qq, { get(target, prop) { if (prop === 'maps') { const maps = Reflect.get(target, prop); if (maps && !proxyCache.has(maps)) { const mapsProxy = new Proxy(maps, { get(t2, p2) { return p2 === 'Geolocation' ? hookTencentGeo(Reflect.get(t2, p2)) : Reflect.get(t2, p2); } }); proxyCache.add(mapsProxy); return mapsProxy; } } return Reflect.get(target, prop); } }); proxyCache.add(_qq); } return _qq; }, set(v) { _qq = v; }, configurable: true }); // ----- Hook 高德地图 AMap.Geolocation ----- const hookAMapGeo = (OrigGeo) => { if (!OrigGeo || proxyCache.has(OrigGeo)) return OrigGeo; const HookedGeo = new Proxy(OrigGeo, { construct(target, args) { const instance = Reflect.construct(target, args); if (proxyCache.has(instance)) return instance; const InstanceProxy = new Proxy(instance, { get(target2, prop) { if (prop === 'getCurrentPosition' || prop === 'getCityInfo') { const fakeMethod = function(suc) { const fd = getFakeData(); const result = { position: [fd.lng, fd.lat], coords: { latitude: fd.lat, longitude: fd.lng, accuracy: fd.accuracy }, accuracy: fd.accuracy, isHighAccuracy: true, status: 1, info: 'SUCCESS', __fake__: true }; if (prop === 'getCityInfo') Object.assign(result, { city: '广州市', province: '广东省', adcode: '440100' }); if (typeof suc === 'function') { setTimeout(() => { suc('complete', result); }, 50); } return result; }; fakeMethod.__hook_name = prop; return fakeMethod; } const val = Reflect.get(target2, prop); return typeof val === 'function' ? val.bind(target2) : val; } }); proxyCache.add(InstanceProxy); return InstanceProxy; } }); proxyCache.add(HookedGeo); return HookedGeo; }; let _amap = window.AMap; Object.defineProperty(window, 'AMap', { get() { if (_amap && !_amap.__amap_hooked__) { const proxy = new Proxy(_amap, { get(target, prop) { if (prop === 'Geolocation') { const geo = Reflect.get(target, prop); return geo ? hookAMapGeo(geo) : geo; } if (prop === '__amap_hooked__') return true; return Reflect.get(target, prop); } }); _amap = proxy; } return _amap; }, set(v) { _amap = v; }, configurable: true }); // ----- Hook 百度地图 BMap.Geolocation ----- const hookBMapGeo = (OrigGeo) => { if (!OrigGeo || proxyCache.has(OrigGeo)) return OrigGeo; const HookedGeo = new Proxy(OrigGeo, { construct(target, args) { const instance = Reflect.construct(target, args); if (proxyCache.has(instance)) return instance; const InstanceProxy = new Proxy(instance, { get(target2, prop) { if (prop === 'getCurrentPosition') { const fakeMethod = function(opts) { const fd = getFakeData(); const result = { latitude: fd.lat, longitude: fd.lng, accuracy: fd.accuracy, point: { lat: fd.lat, lng: fd.lng }, __fake__: true }; if (opts && typeof opts.success === 'function') { setTimeout(() => opts.success({ point: { lat: fd.lat, lng: fd.lng }, accuracy: fd.accuracy }), 50); } return BMap.GeolocationStatusSuccess || 0; }; fakeMethod.__hook_name = prop; return fakeMethod; } const val = Reflect.get(target2, prop); return typeof val === 'function' ? val.bind(target2) : val; } }); proxyCache.add(InstanceProxy); return InstanceProxy; } }); proxyCache.add(HookedGeo); return HookedGeo; }; let _bmap = window.BMap; Object.defineProperty(window, 'BMap', { get() { if (_bmap && !_bmap.__bmap_hooked__) { const proxy = new Proxy(_bmap, { get(target, prop) { if (prop === 'Geolocation') { const geo = Reflect.get(target, prop); return geo ? hookBMapGeo(geo) : geo; } if (prop === '__bmap_hooked__') return true; return Reflect.get(target, prop); } }); _bmap = proxy; } return _bmap; }, set(v) { _bmap = v; }, configurable: true }); // ----- Hook 微信 JSSDK wx.getLocation ----- let _wx = window.wx; if (_wx && !_wx.__wx_hooked__) { const origGetLocation = _wx.getLocation; if (typeof origGetLocation === 'function') { _wx.getLocation = function(opts) { const fd = getFakeData(); const result = { latitude: fd.lat, longitude: fd.lng, accuracy: fd.accuracy, errMsg: 'getLocation:ok', __fake__: true }; if (opts && typeof opts.success === 'function') { setTimeout(() => opts.success(result), 50); } }; _wx.getLocation.__hook_name = 'getLocation'; } _wx.__wx_hooked__ = true; } Object.defineProperty(window, 'wx', { get() { return _wx; }, set(v) { _wx = v; if (_wx && !_wx.__wx_hooked__ && typeof _wx.getLocation === 'function') { const orig = _wx.getLocation; _wx.getLocation = function(opts) { const fd = getFakeData(); const result = { latitude: fd.lat, longitude: fd.lng, accuracy: fd.accuracy, errMsg: 'getLocation:ok', __fake__: true }; if (opts && typeof opts.success === 'function') setTimeout(() => opts.success(result), 50); }; _wx.getLocation.__hook_name = 'getLocation'; _wx.__wx_hooked__ = true; } }, configurable: true }); }; // ========== 注入到主世界 ========== const injectToMainWorld = () => { const script = document.createElement('script'); script.textContent = '(' + mainWorldScript.toString() + `)(${F_LAT}, ${F_LNG});`; const target = document.head || document.documentElement; if (target) { target.appendChild(script); script.remove(); } }; if (document.head || document.documentElement) injectToMainWorld(); else { const obs = new MutationObserver(() => { if (document.head || document.documentElement) { obs.disconnect(); injectToMainWorld(); } }); obs.observe(document, { childList: true, subtree: true }); } // ========== UI ========== let shadowRoot = null; const showToast = (msg, isError = false) => { if (!shadowRoot) return; const color = isError ? 'rgba(255, 59, 48, 0.9)' : 'rgba(52, 199, 89, 0.9)'; const toast = document.createElement('div'); toast.style.cssText = `position:absolute;top:60px;left:50%;transform:translateX(-50%);background:rgba(255,255,255,0.95);border:1px solid ${color};color:${color};padding:14px 24px;border-radius:30px;font-size:15px;font-weight:900;box-shadow:0 8px 24px rgba(0,0,0,0.15);display:flex;align-items:center;gap:10px;white-space:nowrap;z-index:9999;transition:all 0.4s cubic-bezier(0.32, 0.72, 0, 1);opacity:0;backdrop-filter:blur(10px);`; toast.innerHTML = (isError ? '操作失败' : '操作成功') + ``; shadowRoot.appendChild(toast); setTimeout(() => { toast.style.opacity = '1'; }, 10); setTimeout(() => { toast.style.opacity = '0'; toast.style.top = '-50px'; }, 3000); setTimeout(() => { if (toast.parentNode) toast.remove(); }, 3500); }; // ========== Leaflet 动态加载 ========== let leafletLoaded = false; let leafletCallbacks = []; const loadLeaflet = (callback) => { if (leafletLoaded) { callback(); return; } leafletCallbacks.push(callback); if (leafletCallbacks.length > 1) return; const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = 'https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.css'; document.head.appendChild(link); const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.js'; script.onload = () => { leafletLoaded = true; leafletCallbacks.forEach(cb => cb()); leafletCallbacks = []; }; document.head.appendChild(script); }; // ========== 坐标转换(搜索结果 WGS84→GCJ02)========== const _PI = 3.1415926535897932384626; const _a = 6378245.0; const _ee = 0.00669342162296594323; const _transformLat = (x, y) => { let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); ret += (20.0 * Math.sin(6.0 * x * _PI) + 20.0 * Math.sin(2.0 * x * _PI)) * 2.0 / 3.0; ret += (20.0 * Math.sin(y * _PI) + 40.0 * Math.sin(y / 3.0 * _PI)) * 2.0 / 3.0; ret += (160.0 * Math.sin(y / 12.0 * _PI) + 320 * Math.sin(y * _PI / 30.0)) * 2.0 / 3.0; return ret; }; const _transformLng = (x, y) => { let ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); ret += (20.0 * Math.sin(6.0 * x * _PI) + 20.0 * Math.sin(2.0 * x * _PI)) * 2.0 / 3.0; ret += (20.0 * Math.sin(x * _PI) + 40.0 * Math.sin(x / 3.0 * _PI)) * 2.0 / 3.0; ret += (150.0 * Math.sin(x / 12.0 * _PI) + 300.0 * Math.sin(x / 30.0 * _PI)) * 2.0 / 3.0; return ret; }; const wgs84togcj02 = (lng, lat) => { let dlat = _transformLat(lng - 105.0, lat - 35.0); let dlng = _transformLng(lng - 105.0, lat - 35.0); let radlat = lat / 180.0 * _PI; let magic = Math.sin(radlat); magic = 1 - _ee * magic * magic; let sqrtmagic = Math.sqrt(magic); dlat = (dlat * 180.0) / ((_a * (1 - _ee)) / (magic * sqrtmagic) * _PI); dlng = (dlng * 180.0) / (_a / sqrtmagic * Math.cos(radlat) * _PI); return [lng + dlng, lat + dlat]; }; // ========== 地图选点(高德瓦片,GCJ02坐标系,所见即所得)========== let mapInstance = null; let mapMarker = null; let selectedCoord = null; const openMapPicker = () => { loadLeaflet(() => { if (document.getElementById('sx-map-overlay')) return; const overlay = document.createElement('div'); overlay.id = 'sx-map-overlay'; overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;z-index:2147483647;background:#fff;display:flex;flex-direction:column;'; overlay.innerHTML = `