// ==UserScript== // @name OmeTrace // @version 1.0 // @description Captures IP, location, and attempts gender detection on Ome.tv safely // @match https://ome.tv/* // @match https://omegleapp.me/* // @match https://nsfw.omegleapp.me/* // @match https://chatroulette.com/* // @match https://monkey.app/* // @match https://omegleweb.com/* // @match https://thundr.com/* // @match https://umingle.com/* // @grant unsafeWindow // @grant GM_addStyle // @run-at document-start // ==/UserScript== if (window.__OME_OVERLAY_LOADED__) return; window.__OME_OVERLAY_LOADED__ = true; (function () { 'use strict'; // --- CONFIGURATION & CONSTANTS --- const IPINFO_API_KEY = "REPLACE_WITH_YOUR_IPINFO_API_KEY"; // get on https://ipinfo.io/dashboard | looks like c8d4712fa90b63 const GEOAPIFY_API_KEY = "REPLACE_WITH_YOUR_GEOAPIFY_API_KEY"; // get on https://myprojects.geoapify.com -> select Project -> API Keys | looks like 3f8a1c7d9b2e4f6a5c8d1b7e3f2a9c40 const MAP_WIDTH = 300; const MAP_HEIGHT = 220; const MAP_DISPLAY_HEIGHT = 180; const MAP_WATERMARK_HEIGHT = 25; const IPINFO_CACHE_KEY = 'ometrace_ipinfo_cache_v1'; const IPINFO_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours // Generate a random prefix for IDs and class names const randomPrefix = Math.random().toString(36).substring(2, 10); let ipinfoCache = {}; try { const raw = localStorage.getItem(IPINFO_CACHE_KEY); if (raw) ipinfoCache = JSON.parse(raw); } catch (e) { ipinfoCache = {}; } function saveIpinfoCache() { try { localStorage.setItem(IPINFO_CACHE_KEY, JSON.stringify(ipinfoCache)); } catch (e) { } } let isStreamerMode = false; let areModelsLoaded = false; let lastCapturedIP = null; let isProcessingIP = false; let showFaceBoxes = false; const window = unsafeWindow; // --- WEBRTC HOOK TO CAPTURE IP --- window.oRTCPeerConnection = window.oRTCPeerConnection || window.RTCPeerConnection; window.RTCPeerConnection = function (...args) { const pc = new window.oRTCPeerConnection(...args); pc.oAddIceCandidate = pc.addIceCandidate; pc.addIceCandidate = function (iceCandidate, ...rest) { if (iceCandidate && iceCandidate.candidate) { const fields = iceCandidate.candidate.split(" "); if (fields.length > 7 && fields[7] === "srflx") { const ip = fields[4]; if (ip && ip !== lastCapturedIP && !isProcessingIP) { lastCapturedIP = ip; console.log("[OmeTrace] IP Captured:", ip); gatherIPInfo(ip); } } } return pc.oAddIceCandidate(iceCandidate, ...rest); }; return pc; }; window.RTCPeerConnection.prototype = window.oRTCPeerConnection.prototype; // --- GENDER DETECTION --- let activeVideo = null; let detectionInterval = null; let currentCanvas = null; let missCounter = 0; function waitForVideo() { setInterval(() => { const video = document.querySelector("video"); if (!video || !areModelsLoaded) return; if (video !== activeVideo) { activeVideo = video; startDetection(video); } }, 1000); } function startDetection(video) { if (detectionInterval) clearInterval(detectionInterval); detectionInterval = setInterval(async () => { if (!video || video.readyState < 2 || video.videoWidth === 0) { clearCanvas(); return; } try { const detection = await window.faceapi .detectSingleFace(video, new window.faceapi.TinyFaceDetectorOptions({ inputSize: 416, scoreThreshold: 0.3 })) .withAgeAndGender(); if (!detection) { missCounter++; if (missCounter >= 3) { removeGenderFromOverlay(); clearCanvas(); } return; } missCounter = 0; drawBox(video, detection); updateGenderOverlay(detection); } catch (err) { console.log("[OmeTrace] Detection error:", err); } }, 1200); } function drawBox(video, detection) { if (!video.parentElement) return; if (!showFaceBoxes) { clearCanvas(); return; } const rect = video.getBoundingClientRect(); if (!currentCanvas) { currentCanvas = document.createElement("canvas"); currentCanvas.style.position = "absolute"; currentCanvas.style.pointerEvents = "none"; currentCanvas.style.zIndex = "999"; video.parentElement.style.position = "relative"; video.parentElement.appendChild(currentCanvas); } currentCanvas.width = rect.width; currentCanvas.height = rect.height; currentCanvas.style.width = rect.width + "px"; currentCanvas.style.height = rect.height + "px"; currentCanvas.style.top = video.offsetTop + "px"; currentCanvas.style.left = video.offsetLeft + "px"; const displaySize = { width: rect.width, height: rect.height }; window.faceapi.matchDimensions(currentCanvas, displaySize); const resized = window.faceapi.resizeResults(detection, displaySize); const ctx = currentCanvas.getContext("2d"); ctx.clearRect(0, 0, currentCanvas.width, currentCanvas.height); const box = resized.detection.box; ctx.strokeStyle = detection.gender === "female" ? "#ff4da6" : "#3399ff"; ctx.lineWidth = 3; ctx.strokeRect(box.x, box.y, box.width, box.height); ctx.font = "16px Arial"; ctx.fillStyle = ctx.strokeStyle; ctx.fillText(`${detection.gender} (${Math.round(detection.age)})`, box.x, box.y - 8); } function clearCanvas() { if (!currentCanvas) return; const ctx = currentCanvas.getContext("2d"); ctx.clearRect(0, 0, currentCanvas.width, currentCanvas.height); } function updateGenderOverlay(detection) { const infoElement = document.getElementById(randomPrefix + "_Info"); if (!infoElement) return; let genderSpan = infoElement.querySelector('.gender-result'); if (!genderSpan) { genderSpan = document.createElement('span'); genderSpan.className = 'gender-result'; genderSpan.style.color = "#ff9f43"; infoElement.appendChild(genderSpan); } const genderProb = Math.round(detection.genderProbability * 100); genderSpan.innerHTML = `
${detection.gender} (${Math.round(detection.age)}) ${genderProb}%`; } function removeGenderFromOverlay() { const infoElement = document.getElementById(randomPrefix + "_Info"); if (!infoElement) return; const genderSpan = infoElement.querySelector('.gender-result'); if (genderSpan) genderSpan.remove(); } // --- IP INFO FETCHING --- async function gatherIPInfo(ip) { if (isProcessingIP) return; isProcessingIP = true; updateOverlayHTML(`Resolving ${ip}...`); const cached = ipinfoCache[ip]; if (cached && (Date.now() - cached.ts) < IPINFO_CACHE_TTL) { console.log("[OmeTrace] Using cached data for:", ip); await processIpInfo(cached.data); isProcessingIP = false; return; } try { const url = `https://ipinfo.io/${ip}/json?token=${IPINFO_API_KEY}`; const response = await fetch(url); const json = await response.json(); if (!json.status) { console.log("[OmeTrace] Fetched new data for:", ip); ipinfoCache[ip] = { ts: Date.now(), data: json }; saveIpinfoCache(); await processIpInfo(json); } else { throw new Error(`API Error: ${json.error || json.status}`); } } catch (error) { console.error("[OmeTrace] Fetch error:", error); updateOverlayHTML(`Error fetching IP info`); } finally { isProcessingIP = false; } } async function processIpInfo(json) { let mapImageUrl = null; let location = json.loc || null; let ipValue = isStreamerMode ? 'IP HIDDEN' : (json.ip || 'N/A'); let country = json.country || 'N/A'; let city = json.city || 'N/A'; let region = json.region || 'N/A'; let org = json.org || 'N/A'; // Define the base HTML that is always shown const baseHTML = ` ${country}
${city}
${region}
${org}
${ipValue}
`; // Show initial info with loading spinner for map updateOverlayHTML(baseHTML + ` Generating map...`); if (location) { const [lat, lon] = location.split(','); try { mapImageUrl = await fetchMapImage(lat, lon); } catch (e) { console.error("[OmeTrace] Map fetch failed", e); mapImageUrl = null; } } // Update with final content: baseHTML + map (if available) // We pass baseHTML as content. The map is passed separately via argument. // This removes the "Generating map..." text. updateOverlayHTML(baseHTML, mapImageUrl, location); } async function fetchMapImage(lat, lon) { const mapUrl = `https://maps.geoapify.com/v1/staticmap?style=osm-bright&width=${MAP_WIDTH}&height=${MAP_HEIGHT}¢er=lonlat:${lon},${lat}&zoom=12&marker=lonlat:${lon},${lat};color:%23ff0000;size:medium&apiKey=${GEOAPIFY_API_KEY}`; const response = await fetch(mapUrl); if (!response.ok) throw new Error('Failed to fetch map image'); const imgBlob = await response.blob(); const img = new Image(); const imgURL = URL.createObjectURL(imgBlob); return new Promise((resolve, reject) => { img.onload = () => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const imageWidth = MAP_WIDTH; const imageHeight = MAP_HEIGHT; const cropHeight = imageHeight - 35; canvas.width = imageWidth; canvas.height = cropHeight; ctx.drawImage(img, 0, 0, imageWidth, cropHeight, 0, 0, canvas.width, canvas.height); const croppedImageUrl = canvas.toDataURL(); resolve(croppedImageUrl); }; img.onerror = (e) => reject('Image load error: ' + e); img.src = imgURL; }); } // --- UI UPDATE LOGIC --- function updateOverlayHTML(content, mapUrl = null, location = null) { let existingOverlay = document.getElementById(randomPrefix + '_overlay'); if (!existingOverlay) { createBaseOverlay(); existingOverlay = document.getElementById(randomPrefix + '_overlay'); } let ipInfoDiv = document.getElementById(randomPrefix + '_IPInfo'); if (!ipInfoDiv) { ipInfoDiv = document.createElement('div'); ipInfoDiv.id = randomPrefix + '_IPInfo'; const infoP = document.getElementById(randomPrefix + '_Info'); if (infoP) infoP.insertBefore(ipInfoDiv, infoP.firstChild); } if (content) ipInfoDiv.innerHTML = content; let mapWrapper = existingOverlay.querySelector('.map-wrapper'); if (!mapWrapper) { mapWrapper = document.createElement('div'); mapWrapper.className = 'map-wrapper'; mapWrapper.style.cssText = `display: none;`; existingOverlay.appendChild(mapWrapper); } // If there's a map URL, show the map and set the image source if (mapUrl && location) { let mapImg = mapWrapper.querySelector('img'); if (!mapImg) { mapImg = document.createElement('img'); mapWrapper.appendChild(mapImg); } mapImg.src = mapUrl; const [lat, lon] = location.split(','); mapWrapper.onclick = () => window.open(`https://www.openstreetmap.org/?mlat=${lat}&mlon=${lon}#map=12/${lat}/${lon}`, '_blank'); mapWrapper.style.display = 'block'; } else { // If no map, keep it hidden mapWrapper.style.display = 'none'; } } function createBaseOverlay() { const overlay = document.createElement('div'); overlay.id = randomPrefix + '_overlay'; const infoParagraph = document.createElement('p'); infoParagraph.id = randomPrefix + '_Info'; const ipInfoDiv = document.createElement('div'); ipInfoDiv.id = randomPrefix + '_IPInfo'; ipInfoDiv.innerHTML = 'Ready. Waiting for connection...'; infoParagraph.appendChild(ipInfoDiv); const mapWrapper = document.createElement('div'); mapWrapper.className = 'map-wrapper'; mapWrapper.style.cssText = `display: none;`; const buttonsContainer = document.createElement('div'); buttonsContainer.className = 'button-container'; const faceBoxesToggleButton = document.createElement('div'); faceBoxesToggleButton.className = 'overlay-button faceboxes'; faceBoxesToggleButton.textContent = 'Face Boxes'; faceBoxesToggleButton.onclick = window.toggleFaceBoxes; const streamerToggleButton = document.createElement('div'); streamerToggleButton.className = 'overlay-button streamer'; streamerToggleButton.textContent = 'Streamer Mode'; streamerToggleButton.onclick = window.streamerMode; overlay.appendChild(infoParagraph); overlay.appendChild(mapWrapper); overlay.appendChild(buttonsContainer); buttonsContainer.appendChild(faceBoxesToggleButton); buttonsContainer.appendChild(streamerToggleButton); document.body.appendChild(overlay); } // --- TOGGLES --- window.streamerMode = function () { isStreamerMode = !isStreamerMode; const btn = document.querySelector('.streamer'); if (btn) { btn.style.color = isStreamerMode ? '#00ff00' : '#fff'; btn.style.borderColor = isStreamerMode ? 'rgba(0, 255, 0, 0.6)' : 'rgba(0, 217, 255, 0.3)'; } if (lastCapturedIP && ipinfoCache[lastCapturedIP]) processIpInfo(ipinfoCache[lastCapturedIP].data); }; window.toggleFaceBoxes = function () { showFaceBoxes = !showFaceBoxes; const btn = document.querySelector('.faceboxes'); if (btn) { btn.style.color = showFaceBoxes ? '#00ff00' : '#fff'; btn.style.borderColor = showFaceBoxes ? 'rgba(0, 255, 0, 0.6)' : 'rgba(0, 217, 255, 0.3)'; } if (!showFaceBoxes) clearCanvas(); }; // --- INITIALIZATION --- document.addEventListener('DOMContentLoaded', () => { if (document.getElementById(randomPrefix + '_overlay')) return; const faceApiScript = document.createElement("script"); faceApiScript.src = "https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.min.js"; document.head.appendChild(faceApiScript); faceApiScript.onload = async () => { try { const MODEL_URL = 'https://cdn.jsdelivr.net/gh/justadudewhohacks/face-api.js@master/weights'; await window.faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL); await window.faceapi.nets.faceExpressionNet.loadFromUri(MODEL_URL); await window.faceapi.nets.ageGenderNet.loadFromUri(MODEL_URL); areModelsLoaded = true; waitForVideo(); } catch (e) { console.error("Face API failed", e); } }; const fontAwesomeLink = document.createElement("link"); fontAwesomeLink.rel = "stylesheet"; fontAwesomeLink.href = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"; document.head.appendChild(fontAwesomeLink); const style = document.createElement('style'); style.textContent = ` @import url('https://fonts.googleapis.com/css2?family=Ubuntu&display=swap'); #${randomPrefix}_overlay { position: fixed; min-width: 250px; z-index: 9999; background-color: rgba(17, 24, 39, 0.95); box-shadow: 0px 0px 20px rgba(0, 217, 255, 0.2); border: 1px solid rgba(0, 217, 255, 0.4); padding: 15px 25px; bottom: 1.5em; right: 1.5em; border-radius: 12px; transition: all 0.3s ease; color: #fff; font-family: 'Ubuntu', sans-serif; } #${randomPrefix}_overlay p#${randomPrefix}_Info { font-size: 14px; text-align: center; color: #fff; padding-bottom: 15px; user-select: none; display: flex; flex-direction: column; align-items: center; gap: 5px; margin: 0; } #${randomPrefix}_IPInfo { display: flex; flex-direction: column; align-items: center; gap: 5px; margin-bottom: 5px; } #${randomPrefix}_overlay p#${randomPrefix}_Info span { color: #00d6fe; user-select: all; } #${randomPrefix}_overlay p#${randomPrefix}_Info i { margin-right: 5px; width: 15px; } .overlay-button { display: inline-block; user-select: none; cursor: pointer; font-family: 'Ubuntu', sans-serif; font-size: 13px; border-radius: 6px; padding: 8px 16px; color: #fff; background-color: rgba(0, 217, 255, 0.15); border: 1px solid rgba(0, 217, 255, 0.3); transition: all 0.2s ease; } .overlay-button:hover { background-color: rgba(0, 217, 255, 0.25); transform: translateY(-1px); } .button-container { display: flex; justify-content: center; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; } .map-wrapper { margin-bottom: 10px; width: 100%; height: 154px; overflow: hidden; border-radius: 6px; border: 1px solid rgba(0, 217, 255, 0.3); cursor: pointer; position: relative; } .map-wrapper img { width: 100%; height: auto; max-height: 100%; object-fit: cover; position: absolute; top: 0; left: 0; } .gender-result { margin-top: 5px; display: block; } `; document.head.appendChild(style); createBaseOverlay(); }); })();