// ==UserScript== // @name WG Cheat Panel // @namespace wg-cheat-panel // @version 1.0.0 // @description Clean cheat overlay for WorldGuessr — reads coords from Street View iframe, pins the location on the guess map // @author RandomAccount // @match https://www.worldguessr.com/* // @license MIT // @grant none // @run-at document-idle // @downloadURL https://update.greasyfork.icu/scripts/570414/WG%20Cheat%20Panel.user.js // @updateURL https://update.greasyfork.icu/scripts/570414/WG%20Cheat%20Panel.meta.js // ==/UserScript== (() => { "use strict"; // ───────────────────────────────────────────── // State // ───────────────────────────────────────────── let lat = null; let lng = null; let panelOpen = false; let isDragging = false; let dragOffX = 0; let dragOffY = 0; let lastSrc = ""; let leafletMarker = null; let pinActive = false; // ───────────────────────────────────────────── // Coord extraction — parses Google Maps iframe src // ───────────────────────────────────────────── function parseCoords(src) { if (!src) return null; let m; m = src.match(/[?&]location=(-?\d+\.\d+),(-?\d+\.\d+)/); if (m) return { lat: parseFloat(m[1]), lng: parseFloat(m[2]) }; m = src.match(/[?&]q=(-?\d+\.\d+),(-?\d+\.\d+)/); if (m) return { lat: parseFloat(m[1]), lng: parseFloat(m[2]) }; m = src.match(/@(-?\d+\.\d+),(-?\d+\.\d+)/); if (m) return { lat: parseFloat(m[1]), lng: parseFloat(m[2]) }; m = src.match(/!2d(-?\d+\.\d+)!3d(-?\d+\.\d+)/); if (m) return { lat: parseFloat(m[2]), lng: parseFloat(m[1]) }; m = src.match(/cbll=(-?\d+\.\d+),(-?\d+\.\d+)/); if (m) return { lat: parseFloat(m[1]), lng: parseFloat(m[2]) }; return null; } function findCoords() { for (const iframe of document.querySelectorAll("iframe")) { const src = iframe.src || iframe.getAttribute("src") || ""; if (!src.includes("google.com/maps")) continue; const c = parseCoords(src); if (c) return c; } return null; } // ───────────────────────────────────────────── // Leaflet map finder // Uses the same deep recursive scan as the // original working script: walks object properties // up to depth 4 looking for a Leaflet map instance // whose _container matches the leaflet-container el. // ───────────────────────────────────────────── function isLeafletCandidate(obj, container) { return Boolean( obj && typeof obj === "object" && typeof obj.latLngToContainerPoint === "function" && typeof obj.containerPointToLatLng === "function" && typeof obj.getZoom === "function" && obj._container === container ); } function findLeafletMapForContainer(container) { if (!container) return null; const seen = new WeakSet(); function scan(obj, depth) { if (!obj || typeof obj !== "object" || seen.has(obj) || depth < 0) return null; seen.add(obj); if (isLeafletCandidate(obj, container)) return obj; let keys = []; try { keys = Object.getOwnPropertyNames(obj); } catch { return null; } for (const key of keys) { if (key === "parentNode" || key === "children" || key === "childNodes") continue; let val; try { val = obj[key]; } catch { continue; } if (!val || typeof val !== "object") continue; if (isLeafletCandidate(val, container)) return val; const found = scan(val, depth - 1); if (found) return found; } return null; } // Search roots: the container itself, its parent, any #miniMapArea ancestor, then window const roots = [ container, container.parentElement, container.closest("#miniMapArea"), window, ]; for (const root of roots) { if (!root) continue; const found = scan(root, root === window ? 2 : 4); if (found) return found; } return null; } function getLeafletMap() { const containers = document.querySelectorAll(".leaflet-container"); for (const el of containers) { const map = findLeafletMapForContainer(el); if (map) return map; } return null; } // ───────────────────────────────────────────── // Pin management // ───────────────────────────────────────────── // Extract the Leaflet constructor (L) from the map instance itself. // WorldGuessr bundles Leaflet as a module so window.L is often undefined. // But the map object's prototype chain leads back to L.Map, and L.Map // has .addInitHook on it — we can walk up to find the L namespace by // looking for divIcon / marker on the map's constructor exports, // or we can use the map's own addLayer to place a raw marker directly. function getLFromMap(map) { if (window.L) return window.L; // Try to get L from the map's options or internal refs try { // Leaflet attaches _leaflet_id to instances; the constructor is L.Map // Walk the prototype to find the namespace let proto = Object.getPrototypeOf(map); while (proto) { const ctor = proto.constructor; if (ctor && ctor.version && typeof ctor.marker === "function") return ctor; proto = Object.getPrototypeOf(proto); } } catch {} return null; } function placePin(coordLat, coordLng) { const map = getLeafletMap(); if (!map) return false; // Remove existing marker if (leafletMarker) { try { map.removeLayer(leafletMarker); } catch {} leafletMarker = null; } // Build the pin as a raw positioned div injected into the map's pane, // positioned via Leaflet's own latLngToLayerPoint — no L namespace needed. try { const L = getLFromMap(map); if (L) { // Full Leaflet path — proper marker with custom icon const icon = L.divIcon({ className: "", html: `
`, iconSize: [28, 36], iconAnchor: [14, 36], }); leafletMarker = L.marker([coordLat, coordLng], { icon, zIndexOffset: 9999 }).addTo(map); } else { // Fallback: inject a DOM pin directly into the marker pane and // reposition it whenever the map moves, using the map's own projection. const markerPane = map.getPane ? map.getPane("markerPane") : map._panes && map._panes.markerPane; if (!markerPane) return false; const pin = document.createElement("div"); pin.id = "wgcp-leaflet-pin"; pin.style.cssText = ` position: absolute; width: 28px; height: 36px; pointer-events: none; z-index: 9999; filter: drop-shadow(0 3px 8px rgba(99,102,241,0.5)); transform: translate(-14px, -36px); `; pin.innerHTML = ` `; markerPane.appendChild(pin); function positionPin() { try { const pt = map.latLngToLayerPoint([coordLat, coordLng]); pin.style.left = pt.x + "px"; pin.style.top = pt.y + "px"; } catch {} } positionPin(); map.on("move zoom viewreset zoomend moveend", positionPin); // Store as a fake marker object so removePin can clean it up leafletMarker = { _wgcpDom: pin, _wgcpMap: map, _wgcpHandler: positionPin, }; } pinActive = true; return true; } catch { return false; } } function removePin() { if (leafletMarker) { // Real Leaflet marker if (typeof leafletMarker.remove === "function" || typeof leafletMarker.removeFrom === "function") { const map = getLeafletMap(); try { if (map) map.removeLayer(leafletMarker); } catch {} try { leafletMarker.remove(); } catch {} } // DOM fallback marker if (leafletMarker._wgcpDom) { try { leafletMarker._wgcpMap.off("move zoom viewreset zoomend moveend", leafletMarker._wgcpHandler); leafletMarker._wgcpDom.remove(); } catch {} } leafletMarker = null; } // Also clean up any orphaned pin divs document.getElementById("wgcp-leaflet-pin")?.remove(); pinActive = false; } // ───────────────────────────────────────────── // Styles // ───────────────────────────────────────────── const style = document.createElement("style"); style.textContent = ` @import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=DM+Mono:wght@400;500&display=swap'); #wgcp-root { position: fixed; bottom: 24px; left: 24px; z-index: 999999; font-family: 'DM Sans', system-ui, sans-serif; display: flex; flex-direction: column; align-items: flex-start; gap: 10px; } #wgcp-panel { width: 300px; background: #ffffff; border-radius: 20px; box-shadow: 0 0 0 1px rgba(0,0,0,0.06), 0 4px 6px -1px rgba(0,0,0,0.06), 0 24px 48px -8px rgba(0,0,0,0.18); overflow: hidden; display: none; flex-direction: column; transform-origin: bottom left; } #wgcp-panel.open { display: flex; animation: wgcp-pop 0.2s cubic-bezier(0.34,1.56,0.64,1); } @keyframes wgcp-pop { from { opacity:0; transform:scale(0.88) translateY(12px); } to { opacity:1; transform:scale(1) translateY(0); } } /* Header */ #wgcp-header { padding: 14px 14px 0; display: flex; align-items: center; justify-content: space-between; cursor: move; user-select: none; gap: 10px; } #wgcp-logo { width: 32px; height: 32px; border-radius: 10px; background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); display: flex; align-items: center; justify-content: center; flex-shrink: 0; } #wgcp-logo svg { width: 16px; height: 16px; fill: none; stroke: white; stroke-width: 2; stroke-linecap: round; } #wgcp-title-group { flex: 1; min-width: 0; } #wgcp-title { font-size: 14px; font-weight: 700; color: #0f172a; line-height: 1.2; } #wgcp-subtitle { font-size: 11px; color: #94a3b8; font-weight: 500; } #wgcp-close { width: 26px; height: 26px; border-radius: 8px; border: none; background: #f1f5f9; color: #94a3b8; cursor: pointer; font-size: 13px; display: flex; align-items: center; justify-content: center; transition: all 0.15s; flex-shrink: 0; } #wgcp-close:hover { background: #fee2e2; color: #ef4444; } /* Status */ #wgcp-status-wrap { padding: 10px 14px 0; } #wgcp-status { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 999px; background: #f8fafc; border: 1px solid #e2e8f0; font-size: 11px; font-weight: 600; color: #94a3b8; transition: all 0.3s; } #wgcp-status.found { background: #f0fdf4; border-color: #86efac; color: #16a34a; } #wgcp-dot { width: 6px; height: 6px; border-radius: 50%; background: #cbd5e1; flex-shrink: 0; transition: background 0.3s, box-shadow 0.3s; } #wgcp-status.found #wgcp-dot { background: #22c55e; box-shadow: 0 0 0 3px rgba(34,197,94,0.2); animation: wgcp-blink 2s infinite; } @keyframes wgcp-blink { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } } /* Coord cards */ #wgcp-coords { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding: 10px 14px 0; } .wgcp-card { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 10px 11px; transition: all 0.25s; } .wgcp-card.lit { background: #eef2ff; border-color: #c7d2fe; } .wgcp-card-label { font-size: 10px; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: #cbd5e1; margin-bottom: 2px; transition: color 0.25s; } .wgcp-card.lit .wgcp-card-label { color: #a5b4fc; } .wgcp-card-value { font-family: 'DM Mono', monospace; font-size: 14px; font-weight: 500; color: #dde0ff; line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: color 0.25s; } .wgcp-card.lit .wgcp-card-value { color: #4338ca; } /* Map */ #wgcp-map-wrap { margin: 10px 14px 0; border-radius: 12px; overflow: hidden; height: 152px; background: #f8fafc; border: 1px solid #e2e8f0; position: relative; flex-shrink: 0; } #wgcp-map-empty { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; } #wgcp-map-empty svg { width: 26px; height: 26px; stroke: #e2e8f0; fill: none; stroke-width: 1.5; } #wgcp-map-empty span { font-size: 12px; color: #cbd5e1; font-weight: 500; } #wgcp-map-iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: none; display: none; } /* Actions */ #wgcp-actions { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px; padding: 10px 14px 14px; } .wgcp-btn { border: 1px solid #e2e8f0; border-radius: 10px; padding: 9px 6px 8px; font-family: 'DM Sans', system-ui, sans-serif; font-size: 11px; font-weight: 600; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 4px; transition: all 0.15s; background: #f8fafc; color: #475569; line-height: 1.1; text-align: center; } .wgcp-btn svg { width: 15px; height: 15px; fill: none; stroke: #6366f1; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; } .wgcp-btn:hover { background: #f1f5f9; border-color: #c7d2fe; color: #4338ca; transform: translateY(-1px); } .wgcp-btn:active { transform: translateY(0); } .wgcp-btn.primary { background: linear-gradient(135deg, #6366f1, #8b5cf6); border-color: transparent; color: white; box-shadow: 0 2px 8px rgba(99,102,241,0.3); } .wgcp-btn.primary svg { stroke: white; } .wgcp-btn.primary:hover { opacity: 0.9; color: white; border-color: transparent; } .wgcp-btn.danger { background: #fef2f2; border-color: #fecaca; color: #dc2626; } .wgcp-btn.danger svg { stroke: #dc2626; } .wgcp-btn.danger:hover { background: #fee2e2; color: #dc2626; border-color: #fca5a5; } .wgcp-btn.success { background: #f0fdf4; border-color: #86efac; color: #16a34a; } .wgcp-btn.success svg { stroke: #16a34a; } /* FAB */ #wgcp-fab-wrap { position: relative; } #wgcp-fab { width: 46px; height: 46px; border-radius: 14px; border: none; background: linear-gradient(135deg, #6366f1, #8b5cf6); cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 14px rgba(99,102,241,0.45); transition: all 0.2s cubic-bezier(0.34,1.56,0.64,1); } #wgcp-fab:hover { transform: scale(1.08); box-shadow: 0 6px 20px rgba(99,102,241,0.55); } #wgcp-fab:active { transform: scale(0.96); } #wgcp-fab svg { width: 20px; height: 20px; fill: none; stroke: white; stroke-width: 2.5; stroke-linecap: round; transition: transform 0.25s; } #wgcp-fab.open svg { transform: rotate(45deg); } #wgcp-pin-badge { position: absolute; top: -3px; right: -3px; width: 11px; height: 11px; border-radius: 50%; background: #22c55e; border: 2px solid white; display: none; } `; document.head.appendChild(style); // ───────────────────────────────────────────── // Build DOM // ───────────────────────────────────────────── const root = document.createElement("div"); root.id = "wgcp-root"; document.body.appendChild(root); const panel = document.createElement("div"); panel.id = "wgcp-panel"; root.appendChild(panel); panel.innerHTML = `
WG Cheat Panel
Location assistant
Waiting for location…
Latitude
Longitude
No location yet
`; // FAB const fabWrap = document.createElement("div"); fabWrap.id = "wgcp-fab-wrap"; const fab = document.createElement("button"); fab.id = "wgcp-fab"; fab.innerHTML = ``; const pinBadge = document.createElement("div"); pinBadge.id = "wgcp-pin-badge"; fabWrap.appendChild(fab); fabWrap.appendChild(pinBadge); root.appendChild(fabWrap); // ───────────────────────────────────────────── // Refs // ───────────────────────────────────────────── const statusEl = panel.querySelector("#wgcp-status"); const statusText = panel.querySelector("#wgcp-status-text"); const latEl = panel.querySelector("#wgcp-lat"); const lngEl = panel.querySelector("#wgcp-lng"); const latCard = panel.querySelector("#wgcp-lat-card"); const lngCard = panel.querySelector("#wgcp-lng-card"); const mapIframe = panel.querySelector("#wgcp-map-iframe"); const mapEmpty = panel.querySelector("#wgcp-map-empty"); const pinBtn = panel.querySelector("#wgcp-pin-btn"); const copyBtn = panel.querySelector("#wgcp-copy-btn"); const mapsBtn = panel.querySelector("#wgcp-maps-btn"); const closeBtn = panel.querySelector("#wgcp-close"); const header = panel.querySelector("#wgcp-header"); // ───────────────────────────────────────────── // Drag // ───────────────────────────────────────────── header.addEventListener("mousedown", (e) => { if (e.target === closeBtn) return; isDragging = true; const rect = root.getBoundingClientRect(); dragOffX = e.clientX - rect.left; dragOffY = e.clientY - rect.bottom; e.preventDefault(); }); document.addEventListener("mousemove", (e) => { if (!isDragging) return; root.style.left = Math.max(8, e.clientX - dragOffX) + "px"; root.style.bottom = Math.max(8, window.innerHeight - (e.clientY - dragOffY)) + "px"; }); document.addEventListener("mouseup", () => { isDragging = false; }); // ───────────────────────────────────────────── // FAB toggle // ───────────────────────────────────────────── fab.addEventListener("click", () => { panelOpen = !panelOpen; panel.classList.toggle("open", panelOpen); fab.classList.toggle("open", panelOpen); }); closeBtn.addEventListener("click", () => { panelOpen = false; panel.classList.remove("open"); fab.classList.remove("open"); }); // ───────────────────────────────────────────── // Pin button // ───────────────────────────────────────────── function setPinBtnState(active) { if (active) { pinBtn.className = "wgcp-btn danger"; pinBtn.innerHTML = ` Remove `; pinBadge.style.display = "block"; } else { pinBtn.className = "wgcp-btn primary"; pinBtn.innerHTML = ` Pin map `; pinBadge.style.display = "none"; } } pinBtn.addEventListener("click", () => { if (lat === null) return; if (pinActive) { removePin(); setPinBtnState(false); } else { const ok = placePin(lat, lng); if (ok) { setPinBtnState(true); } else { const prev = pinBtn.innerHTML; pinBtn.className = "wgcp-btn"; pinBtn.textContent = "Not ready"; setTimeout(() => { pinBtn.className = "wgcp-btn primary"; pinBtn.innerHTML = prev; }, 1500); } } }); // ───────────────────────────────────────────── // Copy // ───────────────────────────────────────────── copyBtn.addEventListener("click", () => { if (lat === null) return; navigator.clipboard.writeText(`${lat.toFixed(6)}, ${lng.toFixed(6)}`).then(() => { copyBtn.classList.add("success"); copyBtn.innerHTML = `Copied!`; setTimeout(() => { copyBtn.classList.remove("success"); copyBtn.innerHTML = `Copy`; }, 1800); }); }); // ───────────────────────────────────────────── // Open Google Maps // ───────────────────────────────────────────── mapsBtn.addEventListener("click", () => { if (lat === null) return; window.open(`https://www.google.com/maps?q=${lat},${lng}&z=10`, "_blank"); }); // ───────────────────────────────────────────── // UI update // ───────────────────────────────────────────── function updateUI(coords) { if (!coords) return; if (coords.lat === lat && coords.lng === lng) return; lat = coords.lat; lng = coords.lng; statusEl.classList.add("found"); statusText.textContent = "Location acquired"; latEl.textContent = lat.toFixed(5); lngEl.textContent = lng.toFixed(5); latCard.classList.add("lit"); lngCard.classList.add("lit"); const url = `https://www.google.com/maps?q=${lat},${lng}&z=7&output=embed`; if (mapIframe.src !== url) mapIframe.src = url; mapIframe.style.display = "block"; mapEmpty.style.display = "none"; // Move existing pin to new coords on round change if (pinActive) placePin(lat, lng); } // ───────────────────────────────────────────── // Polling // ───────────────────────────────────────────── function poll() { const iframe = document.querySelector("iframe[src*='google.com/maps']"); const src = iframe ? (iframe.src || "") : ""; if (src === lastSrc) return; lastSrc = src; updateUI(findCoords()); } new MutationObserver(() => { updateUI(findCoords()); }).observe(document.body, { subtree: true, childList: true, attributes: true, attributeFilter: ["src"], }); setInterval(poll, 800); poll(); })();