// ==UserScript== // @name 广建智能查寝 // @namespace http://tampermonkey.net/ // @version 10.5 // @description 全链路 Proxy 隐蔽劫持 + 腾讯地图 API 拦截 + 可自由拖拽悬浮窗 + 动态抖动防检测。 // @author Security Researcher // @license MIT // @match *://xsgz.gdcvi.edu.cn/* // @match *://*.gdcvi.edu.cn/* // @match *://*.map.qq.com/* // @match *://*.mapapi.qq.com/* // @match *://*.lbs.qq.com/* // @match *://*.3gimg.qq.com/* // @grant GM_getValue // @grant GM_setValue // @run-at document-start // ==/UserScript== (function() { 'use strict'; const DEFAULT_LAT = 23.736924; const DEFAULT_LNG = 113.087081; let FAKE_LAT = GM_getValue('fake_lat', DEFAULT_LAT); let FAKE_LNG = GM_getValue('fake_lng', DEFAULT_LNG); const getBaseCoords = () => { const jitter = () => (Math.random() - 0.5) * 0.00008; return { lat: FAKE_LAT + jitter(), lng: FAKE_LNG + jitter(), acc: Math.floor(15 + Math.random() * 12) }; }; const getW3CPosition = () => { const c = getBaseCoords(); return { coords: { latitude: c.lat, longitude: c.lng, accuracy: c.acc, altitude: null, altitudeAccuracy: null, heading: null, speed: null }, timestamp: Date.now() }; }; const getTencentPosition = () => { const c = getBaseCoords(); return { module: 'geolocation', type: 'h5', lat: c.lat, lng: c.lng, accuracy: c.acc, adcode: '', nation: '中国' }; }; try { localStorage.clear(); sessionStorage.clear(); } catch (e) {} const nativeFunctions = new WeakSet(); const originalToString = Function.prototype.toString; Function.prototype.toString = new Proxy(originalToString, { apply(target, thisArg, args) { if (nativeFunctions.has(thisArg)) { return `function ${thisArg.name || ''}() { [native code] }`; } return Reflect.apply(target, thisArg, args); } }); nativeFunctions.add(Function.prototype.toString); if (navigator.geolocation) { const cachedMethods = {}; const hookMethod = (originalMethod, name) => { if (cachedMethods[name]) return cachedMethods[name]; const proxy = new Proxy(originalMethod, { apply(target, thisArg, args) { const successCallback = args[0]; const pos = getW3CPosition(); if (typeof successCallback === 'function') { setTimeout(() => successCallback(pos), 400 + Math.random() * 200); } if (name === 'watchPosition') return Math.floor(Math.random() * 100000); } }); nativeFunctions.add(proxy); cachedMethods[name] = proxy; return proxy; }; const geoProxy = new Proxy(navigator.geolocation, { get(target, prop, receiver) { if (prop === 'getCurrentPosition') return hookMethod(target[prop], 'getCurrentPosition'); if (prop === 'watchPosition') return hookMethod(target[prop], 'watchPosition'); if (prop === 'clearWatch') return function() {}; return Reflect.get(target, prop, receiver); } }); const originalDescriptor = Object.getOwnPropertyDescriptor(Navigator.prototype, 'geolocation'); if (originalDescriptor && originalDescriptor.get) { const hookedGetter = new Proxy(originalDescriptor.get, { apply() { return geoProxy; } }); nativeFunctions.add(hookedGetter); Object.defineProperty(Navigator.prototype, 'geolocation', { get: hookedGetter, set: originalDescriptor.set, enumerable: originalDescriptor.enumerable, configurable: false }); } else { Object.defineProperty(navigator, 'geolocation', { get: function() { return geoProxy; }, enumerable: true, configurable: false }); } if (window.Geolocation && window.Geolocation.prototype) { const proto = window.Geolocation.prototype; const origGetCurrent = proto.getCurrentPosition; if (origGetCurrent) { Object.defineProperty(proto, 'getCurrentPosition', { value: hookMethod(origGetCurrent, 'getCurrentPosition'), writable: false, configurable: false }); } const origWatch = proto.watchPosition; if (origWatch) { Object.defineProperty(proto, 'watchPosition', { value: hookMethod(origWatch, 'watchPosition'), writable: false, configurable: false }); } } } const hookedTencent = new WeakSet(); const createGeoProxy = (obj) => { if (!obj || typeof obj !== 'function' || hookedTencent.has(obj)) return obj; hookedTencent.add(obj); const proxy = new Proxy(obj, { construct(target, args) { const instance = Reflect.construct(target, args); const cachedInsMethods = {}; return new Proxy(instance, { get(insTarget, insProp) { if (insProp === 'getLocation' || insProp === 'getIpLocation') { if (cachedInsMethods[insProp]) return cachedInsMethods[insProp]; const methodProxy = new Proxy(insTarget[insProp], { apply(fnTarget, thisArg, fnArgs) { const successCallback = fnArgs[0]; const pos = getTencentPosition(); if (typeof successCallback === 'function') { setTimeout(() => successCallback(pos), 350 + Math.random() * 150); } } }); nativeFunctions.add(methodProxy); cachedInsMethods[insProp] = methodProxy; return methodProxy; } return Reflect.get(insTarget, insProp); } }); } }); nativeFunctions.add(proxy); return proxy; }; const createMapsProxy = (obj) => { if (!obj || typeof obj !== 'object' || hookedTencent.has(obj)) return obj; hookedTencent.add(obj); return new Proxy(obj, { get(target, prop) { const val = Reflect.get(target, prop); if (prop === 'Geolocation') return createGeoProxy(val); return val; }, set(target, prop, value) { if (prop === 'Geolocation') value = createGeoProxy(value); return Reflect.set(target, prop, value); } }); }; const createQQProxy = (obj) => { if (!obj || typeof obj !== 'object' || hookedTencent.has(obj)) return obj; hookedTencent.add(obj); return new Proxy(obj, { get(target, prop) { const val = Reflect.get(target, prop); if (prop === 'maps') return createMapsProxy(val); return val; }, set(target, prop, value) { if (prop === 'maps') value = createMapsProxy(value); return Reflect.set(target, prop, value); } }); }; let _qq = window.qq; if (_qq) _qq = createQQProxy(_qq); Object.defineProperty(window, 'qq', { get() { return _qq; }, set(val) { _qq = createQQProxy(val); }, configurable: true, enumerable: true }); function createInjectUI() { if (window !== window.top) return; const hostId = 'gdcvi-host-' + Math.random().toString(36).substring(2); const host = document.createElement('div'); host.id = hostId; host.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 2147483647;'; document.documentElement.appendChild(host); const shadow = host.attachShadow({ mode: 'closed' }); const style = document.createElement('style'); style.textContent = ` * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; } #fab { position: absolute; top: 100px; right: 20px; width: 50px; height: 50px; background: rgba(40, 167, 69, 0.9); border-radius: 50%; box-shadow: 0 4px 15px rgba(0,0,0,0.3); pointer-events: auto; display: flex; justify-content: center; align-items: center; font-size: 24px; color: white; cursor: pointer; user-select: none; backdrop-filter: blur(5px); transition: transform 0.1s; z-index: 1000; } #fab:active { transform: scale(0.9); } #overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); opacity: 0; pointer-events: none; transition: opacity 0.3s ease; z-index: 998; backdrop-filter: blur(2px); } #overlay.active { opacity: 1; pointer-events: auto; } #bottom-sheet { position: absolute; bottom: -100%; left: 0; width: 100%; background: #fff; border-top-left-radius: 20px; border-top-right-radius: 20px; padding: 20px; box-shadow: 0 -4px 20px rgba(0,0,0,0.15); pointer-events: auto; transition: bottom 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); z-index: 999; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } #bottom-sheet.active { bottom: 0; } .sheet-handle { width: 40px; height: 5px; background: #ddd; border-radius: 3px; margin: 0 auto 15px; } .sheet-title { text-align: center; font-weight: 600; font-size: 16px; margin-bottom: 20px; color: #333; } .input-group { display: flex; gap: 12px; margin-bottom: 20px; } .input-box { flex: 1; display: flex; flex-direction: column; gap: 5px; } .input-box label { font-size: 12px; color: #666; font-weight: 500; } .input-box input { width: 100%; padding: 12px; border: 1px solid #e0e0e0; border-radius: 10px; font-size: 15px; outline: none; background: #f8f9fa; transition: border 0.2s; } .input-box input:focus { border-color: #28a745; background: #fff; } .btn-group { display: flex; gap: 12px; } button { flex: 1; padding: 14px; border: none; border-radius: 12px; font-weight: 600; font-size: 15px; cursor: pointer; color: white; transition: opacity 0.2s; } button:active { opacity: 0.8; } #btn-save { background: linear-gradient(135deg, #28a745, #20c997); box-shadow: 0 4px 10px rgba(40,167,69,0.3); } #btn-reset { background: #6c757d; } #toast { position: absolute; top: 40px; left: 50%; transform: translateX(-50%); padding: 12px 24px; border-radius: 30px; color: white; font-size: 14px; font-weight: 600; opacity: 0; pointer-events: none; transition: opacity 0.3s ease, top 0.3s ease; box-shadow: 0 4px 15px rgba(0,0,0,0.2); white-space: nowrap; z-index: 2000; } .coffee-area { margin-top: 15px; text-align: center; } .coffee-link { font-size: 12px; color: #6c757d; text-decoration: none; border-bottom: 1px dashed #ccc; padding-bottom: 2px; } .coffee-qr-box { display: none; margin-top: 12px; transition: all 0.3s ease; } .coffee-qr-box img { width: 130px; height: 130px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .coffee-tips { font-size: 11px; color: #999; margin-top: 6px; } `; shadow.appendChild(style); const overlay = document.createElement('div'); overlay.id = 'overlay'; shadow.appendChild(overlay); const fab = document.createElement('div'); fab.id = 'fab'; fab.innerText = '📍'; shadow.appendChild(fab); const sheet = document.createElement('div'); sheet.id = 'bottom-sheet'; sheet.innerHTML = `
智能定位控制台
☕ Buy me a coffee
QR Code
Scan to support
`; shadow.appendChild(sheet); const toast = document.createElement('div'); toast.id = 'toast'; shadow.appendChild(toast); const showToast = (msg, isError = false) => { toast.style.backgroundColor = isError ? '#dc3545' : '#28a745'; toast.innerText = msg; setTimeout(() => { toast.style.opacity = '1'; toast.style.top = '60px'; }, 10); setTimeout(() => { toast.style.opacity = '0'; toast.style.top = '40px'; }, 2000); }; const toggleSheet = (show) => { if (show) { sheet.classList.add('active'); overlay.classList.add('active'); } else { sheet.classList.remove('active'); overlay.classList.remove('active'); shadow.getElementById('coffee-qr-box').style.display = 'none'; } }; overlay.addEventListener('click', () => toggleSheet(false)); shadow.querySelector('.sheet-handle').addEventListener('click', () => toggleSheet(false)); shadow.getElementById('btn-coffee').addEventListener('click', (e) => { e.preventDefault(); const qrBox = shadow.getElementById('coffee-qr-box'); qrBox.style.display = (qrBox.style.display === 'block') ? 'none' : 'block'; }); let isDragging = false; let isMoved = false; let startX, startY, startLeft, startTop; const startDrag = (e) => { if (e.type.includes('touch') && e.touches.length > 1) return; isDragging = true; isMoved = false; const clientX = e.type.includes('touch') ? e.touches[0].clientX : e.clientX; const clientY = e.type.includes('touch') ? e.touches[0].clientY : e.clientY; startX = clientX; startY = clientY; startLeft = fab.offsetLeft; startTop = fab.offsetTop; fab.style.transition = 'none'; }; const doDrag = (e) => { if (!isDragging) return; if (e.type.includes('touch') && e.touches.length > 1) { isDragging = false; return; } const clientX = e.type.includes('touch') ? e.touches[0].clientX : e.clientX; const clientY = e.type.includes('touch') ? e.touches[0].clientY : e.clientY; const dx = clientX - startX; const dy = clientY - startY; if (Math.abs(dx) > 3 || Math.abs(dy) > 3) { isMoved = true; e.preventDefault(); } if (isMoved) { let newLeft = startLeft + dx; let newTop = startTop + dy; const maxLeft = window.innerWidth - fab.offsetWidth; const maxTop = window.innerHeight - fab.offsetHeight; fab.style.left = `${Math.max(0, Math.min(newLeft, maxLeft))}px`; fab.style.top = `${Math.max(0, Math.min(newTop, maxTop))}px`; } }; const stopDrag = () => { if (!isDragging) return; isDragging = false; fab.style.transition = 'transform 0.1s'; if (!isMoved) { toggleSheet(true); } }; fab.addEventListener('mousedown', startDrag); fab.addEventListener('touchstart', startDrag, { passive: false }); window.addEventListener('mousemove', doDrag, { passive: false }); window.addEventListener('touchmove', doDrag, { passive: false }); window.addEventListener('mouseup', stopDrag); window.addEventListener('touchend', stopDrag); shadow.getElementById('btn-save').addEventListener('click', () => { const newLat = parseFloat(shadow.getElementById('lat').value); const newLng = parseFloat(shadow.getElementById('lng').value); if (!isNaN(newLat) && !isNaN(newLng)) { GM_setValue('fake_lat', newLat); GM_setValue('fake_lng', newLng); toggleSheet(false); showToast('✅ 坐标已更新,正在刷新...', false); setTimeout(() => location.reload(), 1200); } else { showToast('❌ 输入无效,请确认输入的是数字!', true); } }); shadow.getElementById('btn-reset').addEventListener('click', () => { GM_setValue('fake_lat', DEFAULT_LAT); GM_setValue('fake_lng', DEFAULT_LNG); shadow.getElementById('lat').value = DEFAULT_LAT; shadow.getElementById('lng').value = DEFAULT_LNG; toggleSheet(false); showToast('🏠 已恢复默认宿舍,正在刷新...', false); setTimeout(() => location.reload(), 1200); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', createInjectUI); } else { createInjectUI(); } })();