diff --git a/README.md b/README.md index a5f4df0..e8a1450 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,106 @@ # GhostTrack -Useful tool to track location or mobile number, so this tool can be called osint or also information gathering + +OSINT & Information Gathering tool with a modern Electron desktop GUI. -New update : -```Version 2.2``` +## Version 3.0 - Electron GUI Edition -### Instalation on Linux (deb) -``` -sudo apt-get install git -sudo apt-get install python3 -``` +### What's New -### Instalation on Termux -``` -pkg install git -pkg install python3 +- **Modern Desktop GUI** - Dark-themed Electron interface with sidebar navigation +- **Better IP Tracking** - Dual API fallback (ip-api.com + ipwho.is), proxy/VPN detection, hosting detection +- **Improved Phone Analysis** - Google's libphonenumber (same library used by Android), more formats and validation +- **Expanded Username Search** - 35+ platforms searched concurrently (was 23), with filtering and status indicators +- **My IP Auto-Analysis** - Reveals your IP and automatically shows full geolocation details +- **Portable EXE** - One-click build script to create a standalone Windows executable +- **Custom Window** - Frameless dark window with custom controls + +### Features + +| Feature | Description | +|---------|-------------| +| IP Tracker | Geolocate any IP with country, city, ISP, proxy/VPN detection, and Google Maps link | +| My IP | Discover your public IP with auto-geolocation | +| Phone Tracker | Analyze phone numbers - carrier, type, country, multiple format outputs | +| Username Tracker | Search 35+ social platforms concurrently with found/not-found filtering | + +## Installation & Usage + +### Electron GUI (Recommended) + +**Prerequisites:** [Node.js 18+](https://nodejs.org) + +```bash +git clone https://github.com/HunxByts/GhostTrack.git +cd GhostTrack/electron-app +npm install +npm start ``` -### Usage Tool +### Build Portable EXE (Windows) + +Double-click `build-exe.bat` in the project root, or run: + +```bash +cd electron-app +npm install +npm run build ``` -git clone https://github.com/HunxByts/GhostTrack.git -cd GhostTrack + +The portable `.exe` will be in the `dist/` folder. + +### Legacy CLI (Python) + +The original Python CLI is still available: + +```bash pip3 install -r requirements.txt python3 GhostTR.py ``` -Display on the menu ```IP Tracker``` - - - -on the IP Track menu, you can combo with the seeker tool to get the target IP -
-:zap: Install Seeker : -- Get Seeker -
+## Tech Stack -Display on the menu ```Phone Tracker``` +| Component | Technology | +|-----------|-----------| +| Desktop Framework | Electron 33 | +| IP Geolocation | ip-api.com (primary) + ipwho.is (fallback) | +| Phone Parsing | libphonenumber-js (Google's library) | +| HTTP Client | Axios | +| Build Tool | electron-builder | +| UI | Custom HTML/CSS dark theme | - +### Installation on Linux (deb) +``` +sudo apt-get install git nodejs npm +``` -on this menu you can search for information from the target phone number +### Installation on Termux +``` +pkg install git nodejs +``` -Display on the menu ```Username Tracker``` +## Project Structure - -on this menu you can search for information from the target username on social media +``` +GhostTrack/ +├── electron-app/ # Electron desktop application +│ ├── main.js # Main process (window, IPC handlers) +│ ├── preload.js # Secure bridge between main & renderer +│ ├── package.json # Dependencies & build config +│ ├── src/ # Backend modules +│ │ ├── ip-tracker.js # IP geolocation (dual API) +│ │ ├── phone-tracker.js # Phone number analysis +│ │ └── username-tracker.js # Username OSINT (35+ platforms) +│ └── renderer/ # Frontend +│ ├── index.html # GUI layout +│ ├── styles.css # Dark theme styles +│ └── renderer.js # UI logic & event handling +├── GhostTR.py # Legacy Python CLI +├── build-exe.bat # Windows EXE build script +├── requirements.txt # Python dependencies (legacy) +└── asset/ # Images & branding +```
:zap: Author : diff --git a/build-exe.bat b/build-exe.bat new file mode 100644 index 0000000..ebbe177 --- /dev/null +++ b/build-exe.bat @@ -0,0 +1,103 @@ +@echo off +REM ============================================ +REM GhostTrack v3.0 - Build EXE Script +REM ============================================ +REM This script builds the GhostTrack Electron app +REM into a portable Windows .exe file. +REM +REM Prerequisites: +REM - Node.js 18+ (https://nodejs.org) +REM - npm (comes with Node.js) +REM ============================================ + +title GhostTrack Build Tool +color 0A + +echo. +echo ============================================ +echo GhostTrack v3.0 - Build Tool +echo ============================================ +echo. + +REM Check if Node.js is installed +where node >nul 2>nul +if %ERRORLEVEL% NEQ 0 ( + echo [ERROR] Node.js is not installed! + echo Please install Node.js from https://nodejs.org + echo. + pause + exit /b 1 +) + +REM Check if npm is installed +where npm >nul 2>nul +if %ERRORLEVEL% NEQ 0 ( + echo [ERROR] npm is not installed! + echo Please install Node.js from https://nodejs.org + echo. + pause + exit /b 1 +) + +REM Display versions +echo [INFO] Node.js version: +node --version +echo [INFO] npm version: +npm --version +echo. + +REM Navigate to electron-app directory +cd /d "%~dp0electron-app" + +if not exist "package.json" ( + echo [ERROR] package.json not found in electron-app directory! + echo Make sure you're running this from the GhostTrack root folder. + pause + exit /b 1 +) + +REM Install dependencies +echo [1/3] Installing dependencies... +echo. +call npm install +if %ERRORLEVEL% NEQ 0 ( + echo. + echo [ERROR] Failed to install dependencies! + pause + exit /b 1 +) + +echo. +echo [2/3] Dependencies installed successfully. +echo. + +REM Build the exe +echo [3/3] Building portable EXE... +echo This may take a few minutes on first build. +echo. +call npm run build +if %ERRORLEVEL% NEQ 0 ( + echo. + echo [ERROR] Build failed! + echo Check the error messages above. + pause + exit /b 1 +) + +echo. +echo ============================================ +echo BUILD COMPLETE! +echo ============================================ +echo. +echo Your portable EXE is located in: +echo %~dp0dist\ +echo. +echo Look for: GhostTrack-*-Portable.exe +echo. + +REM Open the dist folder +if exist "%~dp0dist" ( + explorer "%~dp0dist" +) + +pause diff --git a/electron-app/.gitignore b/electron-app/.gitignore new file mode 100644 index 0000000..683b643 --- /dev/null +++ b/electron-app/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +*.exe diff --git a/electron-app/main.js b/electron-app/main.js new file mode 100644 index 0000000..f157739 --- /dev/null +++ b/electron-app/main.js @@ -0,0 +1,85 @@ +const { app, BrowserWindow, ipcMain } = require('electron'); +const path = require('path'); + +// Modules +const ipTracker = require('./src/ip-tracker'); +const phoneTracker = require('./src/phone-tracker'); +const usernameTracker = require('./src/username-tracker'); + +let mainWindow; + +function createWindow() { + mainWindow = new BrowserWindow({ + width: 1100, + height: 750, + minWidth: 900, + minHeight: 600, + title: 'GhostTrack v3.0', + icon: path.join(__dirname, 'assets', 'icon.png'), + frame: false, + transparent: false, + backgroundColor: '#0a0a0f', + webPreferences: { + preload: path.join(__dirname, 'preload.js'), + contextIsolation: true, + nodeIntegration: false, + }, + }); + + mainWindow.loadFile(path.join(__dirname, 'renderer', 'index.html')); + + mainWindow.on('closed', () => { + mainWindow = null; + }); +} + +app.whenReady().then(createWindow); + +app.on('window-all-closed', () => { + app.quit(); +}); + +app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } +}); + +// --- IPC Handlers --- + +// Window controls +ipcMain.on('window-minimize', () => { + mainWindow?.minimize(); +}); + +ipcMain.on('window-maximize', () => { + if (mainWindow?.isMaximized()) { + mainWindow.unmaximize(); + } else { + mainWindow?.maximize(); + } +}); + +ipcMain.on('window-close', () => { + mainWindow?.close(); +}); + +// IP Tracker +ipcMain.handle('track-ip', async (_event, ip) => { + return await ipTracker.trackIP(ip); +}); + +// Show My IP +ipcMain.handle('show-my-ip', async () => { + return await ipTracker.getMyIP(); +}); + +// Phone Number Tracker +ipcMain.handle('track-phone', async (_event, phoneNumber, regionCode) => { + return phoneTracker.trackPhone(phoneNumber, regionCode); +}); + +// Username Tracker +ipcMain.handle('track-username', async (_event, username) => { + return await usernameTracker.trackUsername(username); +}); diff --git a/electron-app/package.json b/electron-app/package.json new file mode 100644 index 0000000..0c93a30 --- /dev/null +++ b/electron-app/package.json @@ -0,0 +1,51 @@ +{ + "name": "ghosttrack", + "version": "3.0.0", + "description": "GhostTrack - OSINT & Information Gathering Tool with Modern GUI", + "main": "main.js", + "author": "HunxByts", + "license": "MIT", + "scripts": { + "start": "electron .", + "build": "electron-builder --win portable", + "build:dir": "electron-builder --win dir", + "build:installer": "electron-builder --win nsis" + }, + "dependencies": { + "axios": "^1.7.9", + "libphonenumber-js": "^1.11.18" + }, + "devDependencies": { + "electron": "^33.3.1", + "electron-builder": "^25.1.8" + }, + "build": { + "appId": "com.ghosttrack.app", + "productName": "GhostTrack", + "directories": { + "output": "../dist" + }, + "files": [ + "**/*", + "!node_modules/.cache/**" + ], + "win": { + "target": [ + { + "target": "portable", + "arch": ["x64"] + } + ], + "icon": "assets/icon.ico" + }, + "portable": { + "artifactName": "GhostTrack-${version}-Portable.exe" + }, + "nsis": { + "oneClick": true, + "perMachine": false, + "allowToChangeInstallationDirectory": false, + "artifactName": "GhostTrack-${version}-Setup.exe" + } + } +} diff --git a/electron-app/preload.js b/electron-app/preload.js new file mode 100644 index 0000000..7c88025 --- /dev/null +++ b/electron-app/preload.js @@ -0,0 +1,18 @@ +const { contextBridge, ipcRenderer } = require('electron'); + +contextBridge.exposeInMainWorld('ghostAPI', { + // Window controls + minimize: () => ipcRenderer.send('window-minimize'), + maximize: () => ipcRenderer.send('window-maximize'), + close: () => ipcRenderer.send('window-close'), + + // IP Tracker + trackIP: (ip) => ipcRenderer.invoke('track-ip', ip), + showMyIP: () => ipcRenderer.invoke('show-my-ip'), + + // Phone Number Tracker + trackPhone: (phone, region) => ipcRenderer.invoke('track-phone', phone, region), + + // Username Tracker + trackUsername: (username) => ipcRenderer.invoke('track-username', username), +}); diff --git a/electron-app/renderer/index.html b/electron-app/renderer/index.html new file mode 100644 index 0000000..c48a3c6 --- /dev/null +++ b/electron-app/renderer/index.html @@ -0,0 +1,152 @@ + + + + + + + GhostTrack v3.0 + + + + + +
+
+ 👻 + GhostTrack v3.0 +
+
+ + + +
+
+ +
+ + + + +
+ + +
+ +
+ + +
+
+
+ + +
+ +
+ +
+
+
+ + +
+ +
+ + + +
+
+
+ + +
+ +
+ + +
+
+
+ +
+
+ + + + diff --git a/electron-app/renderer/renderer.js b/electron-app/renderer/renderer.js new file mode 100644 index 0000000..37ead02 --- /dev/null +++ b/electron-app/renderer/renderer.js @@ -0,0 +1,413 @@ +// ============================================ +// GhostTrack v3.0 - Renderer Process +// ============================================ + +document.addEventListener('DOMContentLoaded', () => { + // --- Window Controls --- + document.getElementById('btn-minimize').addEventListener('click', () => window.ghostAPI.minimize()); + document.getElementById('btn-maximize').addEventListener('click', () => window.ghostAPI.maximize()); + document.getElementById('btn-close').addEventListener('click', () => window.ghostAPI.close()); + + // --- Navigation --- + const navBtns = document.querySelectorAll('.nav-btn'); + const pages = document.querySelectorAll('.page'); + + navBtns.forEach((btn) => { + btn.addEventListener('click', () => { + const targetPage = btn.dataset.page; + + navBtns.forEach((b) => b.classList.remove('active')); + btn.classList.add('active'); + + pages.forEach((p) => p.classList.remove('active')); + document.getElementById(`page-${targetPage}`).classList.add('active'); + }); + }); + + // --- Utility Functions --- + + function setLoading(button, loading) { + const text = button.querySelector('.btn-text'); + const loader = button.querySelector('.btn-loader'); + if (loading) { + text.classList.add('hidden'); + loader.classList.remove('hidden'); + button.disabled = true; + } else { + text.classList.remove('hidden'); + loader.classList.add('hidden'); + button.disabled = false; + } + } + + function createResultCard(label, value, isLink = false) { + const card = document.createElement('div'); + card.className = 'result-card'; + card.innerHTML = ` +
${escapeHtml(label)}
+ `; + return card; + } + + function createBoolCard(label, value) { + const card = document.createElement('div'); + card.className = 'result-card'; + const boolClass = value ? 'badge-true' : 'badge-false'; + const boolText = value ? 'Yes' : 'No'; + card.innerHTML = ` +
${escapeHtml(label)}
+
${boolText}
`; + return card; + } + + function createSection(title) { + const section = document.createElement('div'); + section.className = 'result-section'; + section.textContent = title; + return section; + } + + function showError(container, message) { + container.innerHTML = `
${escapeHtml(message)}
`; + } + + function escapeHtml(str) { + const div = document.createElement('div'); + div.textContent = str; + return div.innerHTML; + } + + // Make links open in external browser + document.addEventListener('click', (e) => { + if (e.target.tagName === 'A' && e.target.dataset.url) { + e.preventDefault(); + require('electron')?.shell?.openExternal?.(e.target.dataset.url); + } + }); + + // --- IP Tracker --- + + const ipInput = document.getElementById('ip-input'); + const ipTrackBtn = document.getElementById('ip-track-btn'); + const ipResults = document.getElementById('ip-results'); + + ipTrackBtn.addEventListener('click', trackIP); + ipInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') trackIP(); + }); + + async function trackIP() { + const ip = ipInput.value.trim(); + if (!ip) { + showError(ipResults, 'Please enter an IP address'); + return; + } + + setLoading(ipTrackBtn, true); + ipResults.innerHTML = '
Tracking IP address...
'; + + try { + const result = await window.ghostAPI.trackIP(ip); + + if (!result.success) { + showError(ipResults, result.error); + return; + } + + const d = result.data; + ipResults.innerHTML = ''; + + // Location section + ipResults.appendChild(createSection('Location')); + const locGrid = document.createElement('div'); + locGrid.className = 'result-grid'; + locGrid.appendChild(createResultCard('IP Address', d.ip)); + locGrid.appendChild(createResultCard('Country', `${d.country} (${d.countryCode})`)); + locGrid.appendChild(createResultCard('Region', `${d.region} (${d.regionCode})`)); + locGrid.appendChild(createResultCard('City', d.city)); + locGrid.appendChild(createResultCard('Continent', `${d.continent} (${d.continentCode})`)); + locGrid.appendChild(createResultCard('Zip / Postal', d.zip || 'N/A')); + locGrid.appendChild(createResultCard('Latitude', String(d.latitude))); + locGrid.appendChild(createResultCard('Longitude', String(d.longitude))); + locGrid.appendChild(createResultCard('Google Maps', d.mapsUrl, true)); + ipResults.appendChild(locGrid); + + // Network section + ipResults.appendChild(createSection('Network')); + const netGrid = document.createElement('div'); + netGrid.className = 'result-grid'; + netGrid.appendChild(createResultCard('ISP', d.isp)); + netGrid.appendChild(createResultCard('Organization', d.org)); + netGrid.appendChild(createResultCard('AS Number', d.as || 'N/A')); + netGrid.appendChild(createResultCard('AS Name', d.asName || 'N/A')); + netGrid.appendChild(createResultCard('Reverse DNS', d.reverse || 'N/A')); + ipResults.appendChild(netGrid); + + // Timezone section + ipResults.appendChild(createSection('Timezone & Other')); + const tzGrid = document.createElement('div'); + tzGrid.className = 'result-grid'; + tzGrid.appendChild(createResultCard('Timezone', d.timezone)); + tzGrid.appendChild(createResultCard('UTC Offset', d.utcOffset)); + tzGrid.appendChild(createResultCard('Currency', d.currency || 'N/A')); + + // Boolean flags + if (d.isMobile !== undefined) { + tzGrid.appendChild(createBoolCard('Mobile Connection', d.isMobile)); + tzGrid.appendChild(createBoolCard('Proxy / VPN', d.isProxy)); + tzGrid.appendChild(createBoolCard('Hosting / Datacenter', d.isHosting)); + } + + // Extras from fallback API + if (d.callingCode) tzGrid.appendChild(createResultCard('Calling Code', d.callingCode)); + if (d.capital) tzGrid.appendChild(createResultCard('Capital', d.capital)); + if (d.flag) tzGrid.appendChild(createResultCard('Flag', d.flag)); + if (d.borders) tzGrid.appendChild(createResultCard('Borders', d.borders)); + + ipResults.appendChild(tzGrid); + } catch (err) { + showError(ipResults, err.message || 'An unexpected error occurred'); + } finally { + setLoading(ipTrackBtn, false); + } + } + + // --- My IP --- + + const myIpBtn = document.getElementById('myip-btn'); + const myIpResults = document.getElementById('myip-results'); + + myIpBtn.addEventListener('click', showMyIP); + + async function showMyIP() { + setLoading(myIpBtn, true); + myIpResults.innerHTML = '
Discovering your IP address...
'; + + try { + const result = await window.ghostAPI.showMyIP(); + + if (!result.success) { + showError(myIpResults, result.error); + return; + } + + myIpResults.innerHTML = ` +
+
${escapeHtml(result.ip)}
+
Your Public IP Address
+
`; + + // Auto-track the IP for full details + const trackResult = await window.ghostAPI.trackIP(result.ip); + if (trackResult.success) { + const d = trackResult.data; + const grid = document.createElement('div'); + grid.className = 'result-grid'; + grid.appendChild(createResultCard('Country', `${d.country} (${d.countryCode})`)); + grid.appendChild(createResultCard('City', d.city)); + grid.appendChild(createResultCard('ISP', d.isp)); + grid.appendChild(createResultCard('Timezone', d.timezone)); + grid.appendChild(createResultCard('Google Maps', d.mapsUrl, true)); + if (d.isMobile !== undefined) { + grid.appendChild(createBoolCard('VPN / Proxy', d.isProxy)); + } + myIpResults.appendChild(grid); + } + } catch (err) { + showError(myIpResults, err.message || 'An unexpected error occurred'); + } finally { + setLoading(myIpBtn, false); + } + } + + // --- Phone Tracker --- + + const phoneInput = document.getElementById('phone-input'); + const phoneRegion = document.getElementById('phone-region'); + const phoneTrackBtn = document.getElementById('phone-track-btn'); + const phoneResults = document.getElementById('phone-results'); + + phoneTrackBtn.addEventListener('click', trackPhone); + phoneInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') trackPhone(); + }); + + async function trackPhone() { + const phone = phoneInput.value.trim(); + if (!phone) { + showError(phoneResults, 'Please enter a phone number'); + return; + } + + const region = phoneRegion.value; + setLoading(phoneTrackBtn, true); + phoneResults.innerHTML = '
Analyzing phone number...
'; + + try { + const result = await window.ghostAPI.trackPhone(phone, region); + + if (!result.success) { + showError(phoneResults, result.error); + return; + } + + const d = result.data; + phoneResults.innerHTML = ''; + + phoneResults.appendChild(createSection('Phone Number Information')); + const grid = document.createElement('div'); + grid.className = 'result-grid'; + grid.appendChild(createResultCard('Country', d.country)); + grid.appendChild(createResultCard('Country Code', d.countryCode)); + grid.appendChild(createResultCard('Calling Code', d.callingCode)); + grid.appendChild(createResultCard('National Number', d.nationalNumber)); + grid.appendChild(createResultCard('Type', d.type)); + phoneResults.appendChild(grid); + + phoneResults.appendChild(createSection('Formatted Numbers')); + const fmtGrid = document.createElement('div'); + fmtGrid.className = 'result-grid'; + fmtGrid.appendChild(createResultCard('International', d.internationalFormat)); + fmtGrid.appendChild(createResultCard('National', d.nationalFormat)); + fmtGrid.appendChild(createResultCard('E.164 Format', d.e164Format)); + fmtGrid.appendChild(createResultCard('URI', d.uri)); + phoneResults.appendChild(fmtGrid); + + phoneResults.appendChild(createSection('Validation')); + const valGrid = document.createElement('div'); + valGrid.className = 'result-grid'; + valGrid.appendChild(createBoolCard('Valid Number', d.isValid)); + valGrid.appendChild(createBoolCard('Possible Number', d.isPossible)); + phoneResults.appendChild(valGrid); + } catch (err) { + showError(phoneResults, err.message || 'An unexpected error occurred'); + } finally { + setLoading(phoneTrackBtn, false); + } + } + + // --- Username Tracker --- + + const usernameInput = document.getElementById('username-input'); + const usernameTrackBtn = document.getElementById('username-track-btn'); + const usernameResults = document.getElementById('username-results'); + + usernameTrackBtn.addEventListener('click', trackUsername); + usernameInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') trackUsername(); + }); + + async function trackUsername() { + const username = usernameInput.value.trim(); + if (!username) { + showError(usernameResults, 'Please enter a username'); + return; + } + + setLoading(usernameTrackBtn, true); + usernameResults.innerHTML = ` +
Searching for "${escapeHtml(username)}" across platforms...
+
`; + + // Animate progress bar + const progressFill = usernameResults.querySelector('.progress-fill'); + let progress = 10; + const progressInterval = setInterval(() => { + if (progress < 90) { + progress += Math.random() * 15; + progressFill.style.width = Math.min(progress, 90) + '%'; + } + }, 500); + + try { + const result = await window.ghostAPI.trackUsername(username); + + clearInterval(progressInterval); + + if (!result.success) { + showError(usernameResults, result.error); + return; + } + + usernameResults.innerHTML = ''; + + // Stats + const stats = document.createElement('div'); + stats.className = 'username-stats'; + stats.innerHTML = ` +
+
${result.totalFound}
+
Found
+
+
+
${result.totalChecked - result.totalFound}
+
Not Found
+
+
+
${result.totalChecked}
+
Checked
+
`; + usernameResults.appendChild(stats); + + // Filter tabs + const filterDiv = document.createElement('div'); + filterDiv.className = 'filter-tabs'; + filterDiv.innerHTML = ` + + + `; + usernameResults.appendChild(filterDiv); + + // Results list + const listContainer = document.createElement('div'); + listContainer.className = 'username-list'; + listContainer.id = 'username-list'; + + // Found items first + const allItems = [...result.found, ...result.notFound]; + + allItems.forEach((item) => { + const el = document.createElement('div'); + el.className = `username-item ${item.found ? 'found' : 'not-found'}`; + el.dataset.filter = item.found ? 'found' : 'not-found'; + el.innerHTML = ` + ${escapeHtml(item.name)} + ${ + item.found + ? `Open` + : '' + } + ${item.found ? 'FOUND' : 'N/A'}`; + listContainer.appendChild(el); + }); + + usernameResults.appendChild(listContainer); + + // Filter functionality + filterDiv.querySelectorAll('.filter-tab').forEach((tab) => { + tab.addEventListener('click', () => { + filterDiv.querySelectorAll('.filter-tab').forEach((t) => t.classList.remove('active')); + tab.classList.add('active'); + + const filter = tab.dataset.filter; + listContainer.querySelectorAll('.username-item').forEach((item) => { + if (filter === 'all') { + item.style.display = ''; + } else { + item.style.display = item.dataset.filter === filter ? '' : 'none'; + } + }); + }); + }); + } catch (err) { + clearInterval(progressInterval); + showError(usernameResults, err.message || 'An unexpected error occurred'); + } finally { + setLoading(usernameTrackBtn, false); + } + } +}); diff --git a/electron-app/renderer/styles.css b/electron-app/renderer/styles.css new file mode 100644 index 0000000..f70c569 --- /dev/null +++ b/electron-app/renderer/styles.css @@ -0,0 +1,673 @@ +/* ============================================ + GhostTrack v3.0 - Dark Hacker Theme + ============================================ */ + +:root { + --bg-primary: #0a0a0f; + --bg-secondary: #0f0f18; + --bg-card: #12121e; + --bg-input: #161625; + --bg-hover: #1a1a2e; + --bg-sidebar: #0c0c14; + + --accent: #00ff88; + --accent-dim: #00cc6a; + --accent-glow: rgba(0, 255, 136, 0.15); + --accent-glow-strong: rgba(0, 255, 136, 0.3); + + --red: #ff4757; + --yellow: #ffa502; + --blue: #3498db; + --cyan: #00d2d3; + --purple: #a855f7; + + --text-primary: #e8e8f0; + --text-secondary: #8888a0; + --text-muted: #555570; + + --border: #1e1e35; + --border-accent: rgba(0, 255, 136, 0.2); + + --radius: 8px; + --radius-lg: 12px; + + --sidebar-width: 240px; + --titlebar-height: 36px; + + --font-mono: 'Consolas', 'Monaco', 'Courier New', monospace; + --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body { + height: 100%; + overflow: hidden; + font-family: var(--font-sans); + background: var(--bg-primary); + color: var(--text-primary); + user-select: none; +} + +/* ============ TITLEBAR ============ */ + +.titlebar { + height: var(--titlebar-height); + background: var(--bg-sidebar); + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--border); + -webkit-app-region: drag; + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; +} + +.titlebar-drag { + display: flex; + align-items: center; + gap: 8px; + padding-left: 14px; +} + +.titlebar-icon { + font-size: 16px; +} + +.titlebar-text { + font-size: 12px; + font-family: var(--font-mono); + color: var(--text-secondary); + letter-spacing: 0.5px; +} + +.titlebar-controls { + display: flex; + -webkit-app-region: no-drag; +} + +.tb-btn { + width: 46px; + height: var(--titlebar-height); + border: none; + background: transparent; + color: var(--text-secondary); + font-size: 12px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.15s, color 0.15s; +} + +.tb-btn:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.tb-close:hover { + background: var(--red); + color: #fff; +} + +/* ============ APP CONTAINER ============ */ + +.app-container { + display: flex; + height: calc(100vh - var(--titlebar-height)); + margin-top: var(--titlebar-height); +} + +/* ============ SIDEBAR ============ */ + +.sidebar { + width: var(--sidebar-width); + min-width: var(--sidebar-width); + background: var(--bg-sidebar); + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + overflow: hidden; +} + +.sidebar-header { + padding: 20px 16px 16px; + text-align: center; + border-bottom: 1px solid var(--border); +} + +.ghost-ascii { + font-family: var(--font-mono); + font-size: 9px; + line-height: 1.15; + color: var(--accent); + text-shadow: 0 0 10px var(--accent-glow-strong); + margin-bottom: 8px; + text-align: left; + padding-left: 30px; +} + +.sidebar-brand { + font-size: 20px; + font-weight: 800; + letter-spacing: 3px; + color: var(--text-primary); +} + +.sidebar-brand .accent { + color: var(--accent); + text-shadow: 0 0 20px var(--accent-glow); +} + +.sidebar-version { + font-size: 10px; + color: var(--text-muted); + font-family: var(--font-mono); + letter-spacing: 1px; + margin-top: 4px; +} + +.nav-items { + flex: 1; + padding: 12px 10px; + display: flex; + flex-direction: column; + gap: 4px; +} + +.nav-btn { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + padding: 12px 16px; + border: 1px solid transparent; + border-radius: var(--radius); + background: transparent; + color: var(--text-secondary); + font-size: 13px; + font-family: var(--font-sans); + cursor: pointer; + transition: all 0.2s ease; + text-align: left; +} + +.nav-btn:hover { + background: var(--bg-hover); + color: var(--text-primary); + border-color: var(--border); +} + +.nav-btn.active { + background: var(--accent-glow); + color: var(--accent); + border-color: var(--border-accent); + font-weight: 600; +} + +.nav-icon { + width: 18px; + height: 18px; + flex-shrink: 0; +} + +.sidebar-footer { + padding: 14px; + border-top: 1px solid var(--border); + text-align: center; +} + +.footer-text { + font-size: 11px; + color: var(--text-muted); + font-family: var(--font-mono); +} + +.footer-text .accent { + color: var(--accent-dim); +} + +/* ============ MAIN CONTENT ============ */ + +.content { + flex: 1; + overflow-y: auto; + padding: 30px 36px; + background: var(--bg-primary); +} + +.content::-webkit-scrollbar { + width: 6px; +} + +.content::-webkit-scrollbar-track { + background: transparent; +} + +.content::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 3px; +} + +.content::-webkit-scrollbar-thumb:hover { + background: var(--text-muted); +} + +/* ============ PAGES ============ */ + +.page { + display: none; + animation: fadeIn 0.25s ease; +} + +.page.active { + display: block; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} + +.page-header { + margin-bottom: 28px; +} + +.page-header h1 { + font-size: 26px; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 6px; +} + +.page-desc { + font-size: 14px; + color: var(--text-secondary); +} + +/* ============ INPUT GROUP ============ */ + +.input-group { + display: flex; + gap: 10px; + margin-bottom: 28px; + max-width: 700px; +} + +.ghost-input { + flex: 1; + padding: 12px 18px; + background: var(--bg-input); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text-primary); + font-size: 14px; + font-family: var(--font-mono); + outline: none; + transition: border-color 0.2s, box-shadow 0.2s; +} + +.ghost-input::placeholder { + color: var(--text-muted); +} + +.ghost-input:focus { + border-color: var(--accent-dim); + box-shadow: 0 0 0 3px var(--accent-glow); +} + +.ghost-select { + padding: 12px 14px; + background: var(--bg-input); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text-primary); + font-size: 13px; + font-family: var(--font-mono); + outline: none; + cursor: pointer; + min-width: 70px; +} + +.ghost-select:focus { + border-color: var(--accent-dim); + box-shadow: 0 0 0 3px var(--accent-glow); +} + +.ghost-select option { + background: var(--bg-secondary); + color: var(--text-primary); +} + +.ghost-btn { + padding: 12px 28px; + background: linear-gradient(135deg, var(--accent-dim), var(--accent)); + border: none; + border-radius: var(--radius); + color: #0a0a0f; + font-size: 14px; + font-weight: 700; + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + min-width: 100px; +} + +.ghost-btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 20px var(--accent-glow-strong); +} + +.ghost-btn:active { + transform: translateY(0); +} + +.ghost-btn.wide { + min-width: 200px; +} + +.ghost-btn:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +/* Button loader spinner */ +.btn-loader { + width: 16px; + height: 16px; + border: 2px solid rgba(10, 10, 15, 0.3); + border-top-color: #0a0a0f; + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + +.btn-loader.hidden, .hidden { + display: none; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* ============ RESULTS ============ */ + +.results-area { + max-width: 900px; +} + +/* Result card grid */ +.result-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + gap: 12px; +} + +.result-card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 14px 18px; + transition: border-color 0.2s; +} + +.result-card:hover { + border-color: var(--border-accent); +} + +.result-label { + font-size: 11px; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 1px; + margin-bottom: 4px; + font-family: var(--font-mono); +} + +.result-value { + font-size: 15px; + color: var(--text-primary); + font-family: var(--font-mono); + word-break: break-all; +} + +.result-value a { + color: var(--accent); + text-decoration: none; +} + +.result-value a:hover { + text-decoration: underline; +} + +.result-value.highlight { + color: var(--accent); + font-weight: 600; +} + +.result-value .badge-true { + color: var(--accent); +} + +.result-value .badge-false { + color: var(--text-muted); +} + +/* Section titles within results */ +.result-section { + margin-top: 20px; + margin-bottom: 12px; + padding-bottom: 8px; + border-bottom: 1px solid var(--border); + font-size: 14px; + font-weight: 600; + color: var(--accent); + letter-spacing: 0.5px; +} + +.result-section:first-child { + margin-top: 0; +} + +/* My IP big display */ +.my-ip-display { + text-align: center; + padding: 40px 20px; +} + +.my-ip-value { + font-size: 42px; + font-weight: 700; + color: var(--accent); + font-family: var(--font-mono); + text-shadow: 0 0 30px var(--accent-glow-strong); + margin-bottom: 8px; + letter-spacing: 2px; +} + +.my-ip-label { + font-size: 13px; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 2px; +} + +/* Username tracker results */ +.username-stats { + display: flex; + gap: 20px; + margin-bottom: 20px; +} + +.stat-box { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 16px 24px; + text-align: center; + min-width: 120px; +} + +.stat-number { + font-size: 28px; + font-weight: 700; + font-family: var(--font-mono); +} + +.stat-number.green { color: var(--accent); } +.stat-number.red { color: var(--red); } +.stat-number.blue { color: var(--blue); } + +.stat-label { + font-size: 11px; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 1px; + margin-top: 4px; +} + +.username-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 8px; +} + +.username-item { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 16px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + font-size: 13px; + transition: border-color 0.2s; +} + +.username-item:hover { + border-color: var(--border-accent); +} + +.username-item.found { + border-left: 3px solid var(--accent); +} + +.username-item.not-found { + border-left: 3px solid var(--text-muted); + opacity: 0.5; +} + +.username-item .platform-name { + flex: 1; + font-weight: 500; + color: var(--text-primary); +} + +.username-item .platform-status { + font-family: var(--font-mono); + font-size: 11px; + font-weight: 600; +} + +.username-item.found .platform-status { + color: var(--accent); +} + +.username-item.not-found .platform-status { + color: var(--text-muted); +} + +.username-item a { + color: var(--accent); + text-decoration: none; + font-family: var(--font-mono); + font-size: 11px; +} + +.username-item a:hover { + text-decoration: underline; +} + +/* Filter tabs for username results */ +.filter-tabs { + display: flex; + gap: 8px; + margin-bottom: 16px; +} + +.filter-tab { + padding: 6px 16px; + border: 1px solid var(--border); + border-radius: 20px; + background: transparent; + color: var(--text-secondary); + font-size: 12px; + font-family: var(--font-mono); + cursor: pointer; + transition: all 0.2s; +} + +.filter-tab:hover { + border-color: var(--accent-dim); + color: var(--text-primary); +} + +.filter-tab.active { + background: var(--accent-glow); + border-color: var(--accent-dim); + color: var(--accent); +} + +/* Error display */ +.error-msg { + background: rgba(255, 71, 87, 0.1); + border: 1px solid rgba(255, 71, 87, 0.3); + border-radius: var(--radius); + padding: 14px 20px; + color: var(--red); + font-family: var(--font-mono); + font-size: 13px; +} + +/* Loading bar for username search */ +.progress-bar { + width: 100%; + height: 4px; + background: var(--bg-input); + border-radius: 2px; + margin-bottom: 20px; + overflow: hidden; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, var(--accent-dim), var(--accent)); + border-radius: 2px; + transition: width 0.3s ease; + box-shadow: 0 0 10px var(--accent-glow); +} + +/* Pulse animation for loading states */ +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +.loading-text { + color: var(--accent); + font-family: var(--font-mono); + font-size: 13px; + animation: pulse 1.5s infinite; + margin-bottom: 16px; +} diff --git a/electron-app/src/ip-tracker.js b/electron-app/src/ip-tracker.js new file mode 100644 index 0000000..cca9a04 --- /dev/null +++ b/electron-app/src/ip-tracker.js @@ -0,0 +1,147 @@ +const axios = require('axios'); + +/** + * Track an IP address using multiple free geolocation APIs for richer data. + * Primary: ip-api.com (no key required, 45 req/min) + * Fallback: ipwho.is + */ +async function trackIP(ip) { + try { + // Primary API: ip-api.com (more reliable, more fields) + const response = await axios.get( + `http://ip-api.com/json/${ip}?fields=status,message,continent,continentCode,country,countryCode,region,regionName,city,district,zip,lat,lon,timezone,offset,currency,isp,org,as,asname,reverse,mobile,proxy,hosting,query`, + { timeout: 10000 } + ); + + if (response.data.status === 'fail') { + throw new Error(response.data.message || 'IP lookup failed'); + } + + const d = response.data; + return { + success: true, + data: { + ip: d.query, + continent: d.continent, + continentCode: d.continentCode, + country: d.country, + countryCode: d.countryCode, + region: d.regionName, + regionCode: d.region, + city: d.city, + district: d.district || 'N/A', + zip: d.zip, + latitude: d.lat, + longitude: d.lon, + timezone: d.timezone, + utcOffset: formatOffset(d.offset), + currency: d.currency, + isp: d.isp, + org: d.org, + as: d.as, + asName: d.asname, + reverse: d.reverse || 'N/A', + isMobile: d.mobile, + isProxy: d.proxy, + isHosting: d.hosting, + mapsUrl: `https://www.google.com/maps/@${d.lat},${d.lon},12z`, + }, + }; + } catch (primaryError) { + // Fallback to ipwho.is + try { + const response = await axios.get(`https://ipwho.is/${ip}`, { + timeout: 10000, + }); + const d = response.data; + + if (!d.success && d.success !== undefined) { + throw new Error(d.message || 'IP lookup failed'); + } + + return { + success: true, + data: { + ip: d.ip, + type: d.type, + continent: d.continent, + continentCode: d.continent_code, + country: d.country, + countryCode: d.country_code, + region: d.region, + regionCode: d.region_code, + city: d.city, + district: 'N/A', + zip: d.postal, + latitude: d.latitude, + longitude: d.longitude, + timezone: d.timezone?.id, + utcOffset: d.timezone?.utc, + currency: 'N/A', + isp: d.connection?.isp, + org: d.connection?.org, + as: d.connection?.asn?.toString(), + asName: d.connection?.domain, + reverse: 'N/A', + isMobile: false, + isProxy: false, + isHosting: false, + mapsUrl: `https://www.google.com/maps/@${d.latitude},${d.longitude},12z`, + callingCode: d.calling_code, + capital: d.capital, + flag: d.flag?.emoji, + borders: d.borders, + }, + }; + } catch (fallbackError) { + return { + success: false, + error: + primaryError.message || + fallbackError.message || + 'Failed to track IP address', + }; + } + } +} + +/** + * Get the user's public IP address using multiple services for reliability. + */ +async function getMyIP() { + const services = [ + { url: 'https://api.ipify.org?format=json', extract: (d) => d.ip }, + { + url: 'https://httpbin.org/ip', + extract: (d) => d.origin, + }, + { + url: 'https://api.my-ip.io/v2/ip.json', + extract: (d) => d.ip, + }, + ]; + + for (const service of services) { + try { + const response = await axios.get(service.url, { timeout: 5000 }); + const ip = service.extract(response.data); + if (ip) { + return { success: true, ip }; + } + } catch { + continue; + } + } + + return { success: false, error: 'Could not determine your public IP' }; +} + +function formatOffset(offsetSeconds) { + if (!offsetSeconds && offsetSeconds !== 0) return 'N/A'; + const hours = Math.floor(Math.abs(offsetSeconds) / 3600); + const minutes = Math.abs(offsetSeconds) % 3600 / 60; + const sign = offsetSeconds >= 0 ? '+' : '-'; + return `UTC${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`; +} + +module.exports = { trackIP, getMyIP }; diff --git a/electron-app/src/phone-tracker.js b/electron-app/src/phone-tracker.js new file mode 100644 index 0000000..663ea94 --- /dev/null +++ b/electron-app/src/phone-tracker.js @@ -0,0 +1,90 @@ +const { + parsePhoneNumber, + isValidPhoneNumber, + isPossiblePhoneNumber, + getCountryCallingCode, + getExampleNumber, + AsYouType, +} = require('libphonenumber-js'); + +/** + * Track and analyze a phone number using Google's libphonenumber (JS port). + * This is the same library used by Android and Google services - more accurate + * than the Python phonenumbers package. + */ +function trackPhone(phoneNumber, defaultRegion = 'US') { + try { + const parsed = parsePhoneNumber(phoneNumber, defaultRegion); + + if (!parsed) { + return { success: false, error: 'Could not parse phone number' }; + } + + const valid = parsed.isValid(); + const possible = parsed.isPossible(); + const numberType = parsed.getType() || 'UNKNOWN'; + const country = parsed.country || defaultRegion; + + // Format the number in various ways + const international = parsed.formatInternational(); + const national = parsed.formatNational(); + const e164 = parsed.format('E.164'); + const uri = parsed.getURI(); + + // Type descriptions + const typeMap = { + MOBILE: 'Mobile', + FIXED_LINE: 'Fixed Line', + FIXED_LINE_OR_MOBILE: 'Fixed Line or Mobile', + TOLL_FREE: 'Toll Free', + PREMIUM_RATE: 'Premium Rate', + SHARED_COST: 'Shared Cost', + VOIP: 'VoIP', + PERSONAL_NUMBER: 'Personal Number', + PAGER: 'Pager', + UAN: 'Universal Access Number', + VOICEMAIL: 'Voicemail', + UNKNOWN: 'Unknown', + }; + + // Country names + const regionNames = new Intl.DisplayNames(['en'], { type: 'region' }); + let countryName = 'Unknown'; + try { + countryName = regionNames.of(country); + } catch { + countryName = country; + } + + let callingCode = ''; + try { + callingCode = '+' + getCountryCallingCode(country); + } catch { + callingCode = 'N/A'; + } + + return { + success: true, + data: { + country: countryName, + countryCode: country, + callingCode: callingCode, + nationalNumber: parsed.nationalNumber, + internationalFormat: international, + nationalFormat: national, + e164Format: e164, + uri: uri, + type: typeMap[numberType] || numberType, + isValid: valid, + isPossible: possible, + }, + }; + } catch (error) { + return { + success: false, + error: error.message || 'Failed to parse phone number. Use format: +1234567890', + }; + } +} + +module.exports = { trackPhone }; diff --git a/electron-app/src/username-tracker.js b/electron-app/src/username-tracker.js new file mode 100644 index 0000000..f7a906c --- /dev/null +++ b/electron-app/src/username-tracker.js @@ -0,0 +1,147 @@ +const axios = require('axios'); + +/** + * Comprehensive username search across 35+ social media platforms. + * Uses proper URL patterns and status code + redirect detection + * for more accurate results than simple HTTP 200 checks. + */ + +const PLATFORMS = [ + // Major platforms + { name: 'GitHub', url: 'https://github.com/{}', icon: 'code' }, + { name: 'Twitter / X', url: 'https://x.com/{}', icon: 'message-circle' }, + { name: 'Instagram', url: 'https://www.instagram.com/{}/', icon: 'camera' }, + { name: 'TikTok', url: 'https://www.tiktok.com/@{}', icon: 'video' }, + { name: 'YouTube', url: 'https://www.youtube.com/@{}', icon: 'play' }, + { name: 'Reddit', url: 'https://www.reddit.com/user/{}', icon: 'message-square' }, + { name: 'LinkedIn', url: 'https://www.linkedin.com/in/{}', icon: 'briefcase' }, + { name: 'Facebook', url: 'https://www.facebook.com/{}', icon: 'users' }, + { name: 'Pinterest', url: 'https://www.pinterest.com/{}/', icon: 'image' }, + + // Developer platforms + { name: 'GitLab', url: 'https://gitlab.com/{}', icon: 'code' }, + { name: 'Bitbucket', url: 'https://bitbucket.org/{}/', icon: 'code' }, + { name: 'Dev.to', url: 'https://dev.to/{}', icon: 'code' }, + { name: 'Stack Overflow', url: 'https://stackoverflow.com/users/?tab=accounts&SearchText={}', icon: 'layers' }, + { name: 'npm', url: 'https://www.npmjs.com/~{}', icon: 'package' }, + { name: 'PyPI', url: 'https://pypi.org/user/{}/', icon: 'package' }, + { name: 'Replit', url: 'https://replit.com/@{}', icon: 'code' }, + + // Creative / Media + { name: 'Behance', url: 'https://www.behance.net/{}', icon: 'pen-tool' }, + { name: 'Dribbble', url: 'https://dribbble.com/{}', icon: 'circle' }, + { name: 'Medium', url: 'https://medium.com/@{}', icon: 'book' }, + { name: 'SoundCloud', url: 'https://soundcloud.com/{}', icon: 'music' }, + { name: 'Spotify', url: 'https://open.spotify.com/user/{}', icon: 'music' }, + { name: 'Flickr', url: 'https://www.flickr.com/people/{}/', icon: 'camera' }, + { name: 'Vimeo', url: 'https://vimeo.com/{}', icon: 'video' }, + { name: 'DeviantArt', url: 'https://www.deviantart.com/{}', icon: 'palette' }, + + // Social / Messaging + { name: 'Tumblr', url: 'https://{}.tumblr.com', icon: 'edit' }, + { name: 'Twitch', url: 'https://www.twitch.tv/{}', icon: 'tv' }, + { name: 'Telegram', url: 'https://t.me/{}', icon: 'send' }, + { name: 'Snapchat', url: 'https://www.snapchat.com/add/{}', icon: 'camera' }, + { name: 'Quora', url: 'https://www.quora.com/profile/{}', icon: 'help-circle' }, + { name: 'We Heart It', url: 'https://weheartit.com/{}', icon: 'heart' }, + + // Professional / Other + { name: 'Product Hunt', url: 'https://www.producthunt.com/@{}', icon: 'award' }, + { name: 'Keybase', url: 'https://keybase.io/{}', icon: 'key' }, + { name: 'About.me', url: 'https://about.me/{}', icon: 'user' }, + { name: 'Gravatar', url: 'https://en.gravatar.com/{}', icon: 'user' }, + { name: 'Patreon', url: 'https://www.patreon.com/{}', icon: 'dollar-sign' }, + { name: 'Linktree', url: 'https://linktr.ee/{}', icon: 'link' }, +]; + +/** + * Check a single platform for username existence. + * Returns result object with found status and URL. + */ +async function checkPlatform(platform, username) { + const url = platform.url.replace('{}', username); + + try { + const response = await axios.get(url, { + timeout: 8000, + maxRedirects: 3, + validateStatus: (status) => status < 500, + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.5', + }, + }); + + const found = response.status === 200; + + return { + name: platform.name, + url: url, + found: found, + status: response.status, + icon: platform.icon, + }; + } catch (error) { + return { + name: platform.name, + url: url, + found: false, + status: error.response?.status || 0, + error: error.code || 'TIMEOUT', + icon: platform.icon, + }; + } +} + +/** + * Search for a username across all platforms concurrently. + * Uses Promise.allSettled for maximum resilience - one failed + * request won't block others. + */ +async function trackUsername(username) { + if (!username || username.trim().length === 0) { + return { success: false, error: 'Username cannot be empty' }; + } + + const cleanUsername = username.trim(); + + try { + // Run all checks concurrently with a batch size to avoid overwhelming + const batchSize = 10; + const results = []; + + for (let i = 0; i < PLATFORMS.length; i += batchSize) { + const batch = PLATFORMS.slice(i, i + batchSize); + const batchResults = await Promise.allSettled( + batch.map((platform) => checkPlatform(platform, cleanUsername)) + ); + + for (const result of batchResults) { + if (result.status === 'fulfilled') { + results.push(result.value); + } + } + } + + const found = results.filter((r) => r.found); + const notFound = results.filter((r) => !r.found); + + return { + success: true, + username: cleanUsername, + totalChecked: results.length, + totalFound: found.length, + found, + notFound, + }; + } catch (error) { + return { + success: false, + error: error.message || 'Username tracking failed', + }; + } +} + +module.exports = { trackUsername, PLATFORMS };