[
  {
    "path": ".dockerignore",
    "content": "__pycache__\n*.pyc\n*.pyo\n*.pyd\n.Python\nenv\nvenv\n.venv\npip-log.txt\npip-delete-this-directory.txt\n.tox\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.log\n.git\n.gitignore\n.mypy_cache\n.pytest_cache\n.hydra\n.DS_Store\n"
  },
  {
    "path": ".gitignore",
    "content": "__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   with no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# poetry\n#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/#use-with-ide\n.pdm.toml\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n.idea/\n\n# VS Code\n.vscode/\n\n# MacOS\n.DS_Store\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM mcr.microsoft.com/playwright/python:v1.49.0-jammy\n\nWORKDIR /app\n\n# Install Python dependencies\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\n\n# Force-install Chromium that matches the pip-installed Playwright version.\n# This guarantees the chromium_headless_shell binary path matches whatever\n# playwright pip resolves to, even if the base image's bundled browser is\n# from a different release.\nRUN playwright install chromium\n\n# Copy application code\nCOPY . .\n\nEXPOSE 8000\n\nCMD [\"python\", \"-m\", \"uvicorn\", \"the_big_brother.gui.main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\", \"--reload\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 The Big Brother\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\n<br/>\n\n# ◆ 👁️THE BIG BROTHER · V5.0 👁️◆\n<br/>\n\n<img width=\"3910\" height=\"1088\" alt=\"x\" src=\"https://github.com/user-attachments/assets/94879543-dac9-40c0-ab1c-f0a583513ba1\" />\n\n<br/>\n\n```text\n               ╔══════════════════════════════════════════════════════════════════════════╗\n               ║                                                                          ║\n               ║             ████████╗██╗  ██╗███████╗    ██████╗ ██╗ ██████╗             ║\n               ║             ╚══██╔══╝██║  ██║██╔════╝    ██╔══██╗██║██╔════╝             ║\n               ║                ██║   ███████║█████╗      ██████╔╝██║██║  ███╗            ║\n               ║                ██║   ██╔══██║██╔══╝      ██╔══██╗██║██║   ██║            ║\n               ║                ██║   ██║  ██║███████╗    ██████╔╝██║╚██████╔╝            ║\n               ║                ╚═╝   ╚═╝  ╚═╝╚══════╝    ╚═════╝ ╚═╝ ╚═════╝             ║\n               ║                                                                          ║\n               ║        ██████╗ ██████╗  ██████╗ ████████╗██╗  ██╗███████╗██████╗         ║\n               ║        ██╔══██╗██╔══██╗██╔═══██╗╚══██╔══╝██║  ██║██╔════╝██╔══██╗        ║\n               ║        ██████╔╝██████╔╝██║   ██║   ██║   ███████║█████╗  ██████╔╝        ║\n               ║        ██╔══██╗██╔══██╗██║   ██║   ██║   ██╔══██║██╔══╝  ██╔══██╗        ║\n               ║        ██████╔╝██║  ██║╚██████╔╝   ██║   ██║  ██║███████╗██║  ██║        ║\n               ║        ╚═════╝ ╚═╝  ╚═╝ ╚═════╝    ╚═╝   ╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝        ║\n               ║                                                                          ║\n               ║                   ◇ THE EYE THAT NEVER BLINKS — V5.0 ◇                   ║\n               ║                   ULTRA-DEEP OSINT · 21 INTEL MODULES                    ║\n               ║                                                                          ║\n               ╚══════════════════════════════════════════════════════════════════════════╝\n```\n\n<br/>\n\n\n**Advanced OSINT Framework · Premium Holographic Dashboard · 21 Intel Modules**\n\n[![Version](https://img.shields.io/badge/version-5.0.0-ff0050.svg?style=for-the-badge&logo=v&logoColor=white)](https://github.com/chadi0x/the-big-brother/releases)\n[![License](https://img.shields.io/badge/license-MIT-00f0ff.svg?style=for-the-badge)](LICENSE)\n[![Python](https://img.shields.io/badge/python-3.10+-ff0050.svg?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/)\n[![Docker](https://img.shields.io/badge/docker-ready-00f0ff.svg?style=for-the-badge&logo=docker&logoColor=white)](https://www.docker.com/)\n[![Status](https://img.shields.io/badge/STATUS-OPERATIONAL-00ff8c.svg?style=for-the-badge)](https://github.com/chadi0x/)\n\n[`★ WHAT'S NEW`](#-whats-new-in-v50) · [`★ MODULES`](#-the-21-modules) · [`★ INSTALL`](#-installation) · [`★ PREMIUM`](#-premium-projects) · [`★ TELEGRAM`](https://t.me/hisoka0morow)\n\n</div>\n\n---\n\n## ⟦ OVERVIEW ⟧\n\n**The Big Brother V5.0** is a weaponized OSINT framework. Built for Red Teams, Threat Hunters, and Cyber-Investigators — V5 introduces the **AI Analyst** orchestrator that runs every relevant module in parallel and synthesizes one unified threat profile per target.\n\n```\n   ┌─────────────────────────────────────────────────────────────┐\n   │  21 MODULES   ·   1 UNIFIED ENGINE   ·   FULL DOCKER STACK  │\n   │  HOLOGRAPHIC HUD · PARTICLE GRID · ANIMATED INTEL TIMELINE  │\n   └─────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## 🆕 WHAT'S NEW IN V5.0\n\n### ◆ Six brand-new intel modules\n\n| Tool | Capability |\n|:--|:--|\n| 🤖 **AI ANALYST** | Auto-detects target type (email · domain · IP · username), fans out to every relevant module in parallel, synthesizes a unified 0-100 risk score + narrative findings + raw payload dump |\n| 🌐 **DOMAIN ORACLE** | WHOIS (RDAP) · 7 DNS record types · SPF/DMARC/DKIM grading (A-F) · HTTP security-headers grade · TLS metadata · crt.sh subdomain enum · reverse-IP neighbors · global hygiene score |\n| 📨 **MAIL TRACER** | MX validity · SPF/DMARC posture · disposable/role/free-provider detection · Gravatar identity bind · 0-100 deliverability trust score with factor breakdown |\n| 🐙 **CODE HUNTER** | GitHub OSINT via public API · top repos · language stats · **public commit-email harvesting** · 24h activity heatmap · org enumeration · influence score |\n| 🕰️ **WAYBACK SPECTRE** | Wayback Machine CDX timeline · yearly distribution bars · auto-flags sensitive historical paths (admin · `.env` · `.git` · backups · `.bak` · `id_rsa` · 25+ patterns) |\n| 📋 **PASTE DRAGNET** | Multi-site paste hunter across 12 services (Pastebin · Ghostbin · Rentry · Paste.ee · 0bin · JustPaste · Hastebin · Ideone · DPaste · etc) via DDG dorking · auto-flags critical hits |\n\n\n---\n\n## ⚡ THE 21 MODULES\n\n<div align=\"center\">\n\n```text\n┌─────────────────────────────────────────────────────────────────┐\n│  ⟦ SYNTHESIS ⟧                                                  │\n├─────────────────────────────────────────────────────────────────┤\n│  🤖  AI ANALYST       ── cross-module synthesis · NEW           │\n├─────────────────────────────────────────────────────────────────┤\n│  ⟦ IDENTITY ⟧                                                   │\n├─────────────────────────────────────────────────────────────────┤\n│  🔍  TARGET PROFILER  ── 200+ social enum + biometric capture   │\n│  👻  PHANTOM ID       ── cross-platform identity correlation    │\n│  🔓  BREACH VAULT     ── HaveIBeenPwned + paste-dump aggregator │\n│  👣  DIGITAL FOOTPRINT ─ phone + email enumeration              │\n│  📨  MAIL TRACER      ── email forensics · NEW                  │\n│  🐙  CODE HUNTER      ── GitHub OSINT · NEW                     │\n├─────────────────────────────────────────────────────────────────┤\n│  ⟦ INTEL ⟧                                                      │\n├─────────────────────────────────────────────────────────────────┤\n│  📡  SIGINT SWEEP     ── Reddit · HN · News · X aggregator      │\n│  🗺️   SHADOW MAP       ── AbuseIPDB · VT · URLhaus reputation    │\n│  📋  PASTE DRAGNET    ── 12-site paste hunter · NEW             │\n│  🧅  DARK WEB         ── Ahmia + ransomware leak feeds          │\n│  🕰️   WAYBACK SPECTRE ── archive timeline + leak flags · NEW    │\n├─────────────────────────────────────────────────────────────────┤\n│  ⟦ INFRASTRUCTURE ⟧                                             │\n├─────────────────────────────────────────────────────────────────┤\n│  🌐  DOMAIN ORACLE    ── deep domain intel + posture · NEW      │\n│  🕸️   NETWORK MAPPER  ── async ports + pyvis network graph      │\n│  🔒  SSL SENTINEL     ── certificate chain + SAN extraction     │\n│  🪙  CRYPTO           ── BTC/ETH blockchain wallet recon        │\n├─────────────────────────────────────────────────────────────────┤\n│  ⟦ MEDIA · GEO ⟧                                                │\n├─────────────────────────────────────────────────────────────────┤\n│  📸  EXIF X-RAY       ── metadata + GPS extraction              │\n│  🛠️   DORK STUDIO     ── Google/Shodan/GitHub dork generator    │\n│  🛰️   GEOINT SPY      ── multi-engine coordinate intel          │\n│  ✈️   SKY RADAR       ── live OpenSky aircraft tracking          │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n</div>\n\n---\n\n## 🚀 INSTALLATION\n\n### ⟦ PROTOCOL A · Docker (Recommended) ⟧\n\n```bash\n# 1. Clone the grid\ngit clone https://github.com/chadi0x/TheBigBrother.git\ncd TheBigBrother\n\n# 2. Boot the eye\ndocker-compose up --build -d\n```\n\n> ▸ **Access:** `http://localhost:8000`\n> ▸ **Stop:** `docker-compose down`\n> ▸ **Logs:** `docker-compose logs -f`\n\n### ⟦ PROTOCOL B · Manual Bare-Metal ⟧\n\n**Prerequisites:** Python `3.10+` · Playwright\n\n```bash\n# 1. Environment\npython -m venv venv\nsource venv/bin/activate          # Windows: venv\\Scripts\\activate\n\n# 2. Dependencies\npip install -r requirements.txt\nplaywright install chromium\n\n# 3. Launch\nuvicorn the_big_brother.gui.main:app --host 0.0.0.0 --port 8000\n```\n\n---\n\n## 📸 SCREENSHOTS\n\n<div align=\"center\">\n\n<img width=\"1675\" height=\"840\" alt=\"V5 dashboard\" src=\"https://github.com/user-attachments/assets/548d4613-d53c-4d89-85ea-fb538b5bedff\" />\n\n\n\n</div>\n\n---\n\n## ◆ THE STACK\n\n| Layer | Tech |\n|:--|:--|\n| **Backend** | FastAPI · Python 3.10+ · async-first orchestration |\n| **Frontend** | Vanilla JS · Leaflet · custom particle engine |\n| **Recon** | Playwright (headless validation) · pyvis · dnspython · python-whois · DuckDuckGo |\n| **Container** | Docker + docker-compose · single-command deploy |\n\n```\n                      ┌──────────────┐\n                      │   BROWSER    │\n                      │  (HUD · JS)  │\n                      └──────┬───────┘\n                             │ /api/*\n                             ▼\n                      ┌──────────────┐\n                      │   FASTAPI    │  ← 22 endpoints\n                      └──────┬───────┘\n                             │\n            ┌────────────────┼────────────────┐\n            ▼                ▼                ▼\n       ┌─────────┐      ┌─────────┐     ┌─────────┐\n       │ SCANNER │      │ MODULES │     │HEADLESS │\n       │  (200+  │      │  (21)   │     │PLAYWRGHT│\n       │  sites) │      │         │     │  valid. │\n       └─────────┘      └─────────┘     └─────────┘\n            │                │                │\n            └────────────────▼────────────────┘\n                  PUBLIC OSINT ENDPOINTS\n            (rdap · crt.sh · wayback · github · etc)\n```\n\n---\n\n## 🔥 PREMIUM PROJECTS\n\nFor operators who need the full arsenal — exclusive offerings via Telegram.\n\n- 👁️ **TheBigBrother God-EYE** · Proprietary servers · extended API access · zero-key deep intel\n- 🐦 **X GodMode** · Twitter/X automation suite — engagement & sentiment engineering\n- 🎵 **Amplifior** · Autonomous TikTok empire builder\n- 🎣 **Psychopath** · Engineered campaign deployment with AI conversational bots\n- 🛠️ **Custom Builds** · 200+ ready-to-deploy operational frameworks. If it doesn't exist, we build it.\n\n<div align=\"center\">\n\n**[▸ INQUIRE · Telegram @hisoka0morow](https://t.me/hisoka0morow)**\n\n</div>\n\n---\n\n## ⚠️ DISCLAIMER\n\nThis framework is for **educational research and authorized security testing only**. Users are solely responsible for compliance with all applicable laws. The author assumes zero liability for misuse.\n\n> **CLASSIFIED · AUTHORIZED PERSONNEL ONLY**\n\n---\n\n## 📞 SUPPORT · COMMUNITY\n\n- **Creator / Architect** — `CHADI0X`\n- **Telegram (direct inquiries)** — [@hisoka0morow](https://t.me/hisoka0morow)\n- **GitHub** — [@chadi0x](https://github.com/chadi0x)\n\n---\n\n## 📜 LICENSE\n\nMIT License — see [LICENSE](LICENSE)\n\n---\n\n<div align=\"center\">\n\n```\n◆ ─────────────────────────────────────────────────── ◆\n   \"In the digital age, anonymity is a luxury.\n    Information is the currency of power.\"\n                              ── CHADI0X\n◆ ─────────────────────────────────────────────────── ◆\n```\n\n**V5.0.0 ·  THE EYE THAT NEVER BLINKS  · ◇**\n\n*Built by CHADI0X · Quantum OSINT Surveillance Grid*\n\n</div>\n"
  },
  {
    "path": "VERSION",
    "content": "2.1.0\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "services:\n  the-big-brother:\n    build: .\n    ports:\n      - \"8000:8000\"\n    volumes:\n      - .:/app\n    environment:\n      - PYTHONUNBUFFERED=1\n    restart: unless-stopped\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools>=61.0\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"the-big-brother\"\nversion = \"2.1.0\"\ndescription = \"Advanced OSINT Tool for Username Intelligence & Visual Reconnaissance\"\nreadme = \"README.md\"\nrequires-python = \">=3.9\"\nlicense = {text = \"MIT\"}\nauthors = [\n    {name = \"CHADI0X\", email = \"chadi0x@example.com\"}\n]\nkeywords = [\"osint\", \"reconnaissance\", \"username\", \"reverse-image-search\", \"intelligence\"]\nclassifiers = [\n    \"Development Status :: 4 - Beta\",\n    \"Intended Audience :: Information Technology\",\n    \"License :: OSI Approved :: MIT License\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3.9\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Topic :: Security\",\n]\n\ndependencies = [\n    \"fastapi>=0.104.0\",\n    \"uvicorn>=0.24.0\",\n    \"requests>=2.31.0\",\n    \"beautifulsoup4>=4.12.0\",\n    \"playwright>=1.40.0\",\n    \"duckduckgo-search>=3.9.0\",\n    \"tomli>=2.0.0; python_version<'3.11'\",\n    \"pandas>=2.0.0\",\n    \"requests-futures>=1.0.0\",\n    \"colorama>=0.4.6\",\n    \"openpyxl>=3.1.0\",\n]\n\n[project.optional-dependencies]\ndev = [\n    \"pytest>=7.0.0\",\n    \"black>=23.0.0\",\n    \"mypy>=1.0.0\",\n]\n\n[project.urls]\nHomepage = \"https://github.com/chadi0x/the-big-brother\"\nRepository = \"https://github.com/chadi0x/the-big-brother\"\nIssues = \"https://github.com/chadi0x/the-big-brother/issues\"\n\n[project.scripts]\nthe-big-brother = \"the_big_brother.__main__:main\"\n\n[tool.setuptools]\npackages = [\"the_big_brother\"]\n\n[tool.setuptools.package-data]\nthe_big_brother = [\"py.typed\", \"gui/**/*\", \"resources/**/*\"]\n"
  },
  {
    "path": "requirements.txt",
    "content": "fastapi>=0.104.0\nuvicorn>=0.24.0\nrequests>=2.31.0\nbeautifulsoup4>=4.12.0\nplaywright==1.49.0\nduckduckgo-search>=3.9.0\ntomli>=2.0.0\npandas>=2.0.0\nrequests-futures>=1.0.0\ncolorama>=0.4.6\nopenpyxl>=3.1.0\nholehe>=1.60\nphonenumbers>=8.12.0\ndnspython>=2.4.0\npyvis>=0.3.0\nnetworkx>=3.0.0\nPillow>=9.0.0\npython-whois>=0.9.0\npython-multipart>=0.0.6\naiohttp>=3.9.0\n"
  },
  {
    "path": "the_big_brother/__init__.py",
    "content": "\"\"\" The Big Brother Module\n\nThis module contains the main logic to search for usernames at social\nnetworks.\n\n\"\"\"\n\nfrom importlib.metadata import version as pkg_version, PackageNotFoundError\nimport pathlib\nimport tomli\n\n\ndef get_version() -> str:\n    \"\"\"Fetch the version number of the installed package.\"\"\"\n    try:\n        return pkg_version(\"the_big_brother\")\n    except PackageNotFoundError:\n        # Try reading from pyproject.toml (setuptools format)\n        pyproject_path: pathlib.Path = pathlib.Path(__file__).resolve().parent.parent / \"pyproject.toml\"\n        if pyproject_path.exists():\n            with pyproject_path.open(\"rb\") as f:\n                pyproject_data = tomli.load(f)\n            # Use setuptools format: project.version instead of tool.poetry.version\n            if \"project\" in pyproject_data and \"version\" in pyproject_data[\"project\"]:\n                return pyproject_data[\"project\"][\"version\"]\n        \n        # Fallback to VERSION file\n        version_path: pathlib.Path = pathlib.Path(__file__).resolve().parent.parent / \"VERSION\"\n        if version_path.exists():\n            return version_path.read_text().strip()\n        \n        return \"2.1.0\"  # Final fallback\n\n# This variable is only used to check for ImportErrors induced by users running as script rather than as module or package\nimport_error_test_var = None\n\n__shortname__   = \"The Big Brother\"\n__longname__    = \"The Big Brother: Find Usernames Across Social Networks\"\n__version__     = get_version()\n\n# Update check disabled for now or point to new repo if exists\nforge_api_latest_release = \"\"\n"
  },
  {
    "path": "the_big_brother/__main__.py",
    "content": "#! /usr/bin/env python3\n\n\"\"\"\nSherlock: Find Usernames Across Social Networks Module\n\nThis module contains the main logic to search for usernames at social\nnetworks.\n\"\"\"\n\nimport sys\n\n\nif __name__ == \"__main__\":\n    # Check if the user is using the correct version of Python\n    python_version = sys.version.split()[0]\n\n    if sys.version_info < (3, 9):\n        print(f\"The Big Brother requires Python 3.9+\\nYou are using Python {python_version}, which is not supported by The Big Brother.\")\n        sys.exit(1)\n\n    from the_big_brother import scanner\n    scanner.main()\n"
  },
  {
    "path": "the_big_brother/gui/__init__.py",
    "content": ""
  },
  {
    "path": "the_big_brother/gui/main.py",
    "content": "from fastapi import FastAPI, BackgroundTasks, Response, UploadFile, File, Form\nfrom fastapi.staticfiles import StaticFiles\nfrom fastapi.middleware.cors import CORSMiddleware\nfrom pydantic import BaseModel\nfrom uuid import uuid4\nimport os\nimport sys\nimport io\nimport csv\nfrom typing import List, Optional\n\n# Add parent directory to path to allow imports\nsys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), \"../..\")))\n\nfrom the_big_brother.scanner import scan, SitesInformation, QueryNotify, QueryStatus\nfrom the_big_brother.image_grabber import fetch_images, fetch_images_with_diag\nfrom the_big_brother.reverse_search import ReverseImageSearcher\nfrom the_big_brother.validators.headless_validator import HeadlessValidator\nfrom the_big_brother.modules.digital_footprint import get_phone_info, run_holehe\nfrom the_big_brother.modules.network_mapper import scan_target, generate_network_map\nfrom the_big_brother.modules.dark_watch import search_dark_web\nfrom the_big_brother.modules.crypto_analyzer import analyze_crypto\nfrom the_big_brother.modules.ssl_sentinel import get_ssl_info\nfrom the_big_brother.modules.exif_analyzer import get_exif_data\nfrom the_big_brother.modules.dork_studio import generate_dorks\nfrom the_big_brother.modules.geoint_spy import get_geoint_data\nfrom the_big_brother.modules.flight_radar import get_flight_radar\n\n# New V4 modules\nfrom the_big_brother.modules.phantom_id import phantom_id_search\nfrom the_big_brother.modules.breach_vault import breach_vault_search\nfrom the_big_brother.modules.sigint_sweep import sigint_sweep\nfrom the_big_brother.modules.shadow_map import shadow_map_analyze\n\n# New V5 modules\nfrom the_big_brother.modules.domain_oracle import domain_oracle\nfrom the_big_brother.modules.mail_tracer import mail_tracer\nfrom the_big_brother.modules.code_hunter import code_hunter\nfrom the_big_brother.modules.wayback_spectre import wayback_spectre\nfrom the_big_brother.modules.paste_dragnet import paste_dragnet\nfrom the_big_brother.modules.ai_analyst import ai_analyst\n\nclass FootprintRequest(BaseModel):\n    query: str\n    type: str # \"email\" or \"phone\"\n\nclass NetworkRequest(BaseModel):\n    domain: str\n\nclass DarkRequest(BaseModel):\n    query: str\n\nclass CryptoRequest(BaseModel):\n    address: str\n    coin: str\n\nclass SSLRequest(BaseModel):\n    domain: str\n\nclass ExifRequest(BaseModel):\n    url: str\n\nclass DorkRequest(BaseModel):\n    target: str\n    domain: str = \"\"\n\nclass DeepSearchRequest(BaseModel):\n    image_url: str\n\nclass GeointRequest(BaseModel):\n    lat: str\n    lon: str\n\nclass FlightRequest(BaseModel):\n    lat: float\n    lon: float\n    radius: float = 100\n\nclass PhantomRequest(BaseModel):\n    username: str\n\nclass BreachRequest(BaseModel):\n    query: str\n    type: str = \"email\"\n\nclass SigintRequest(BaseModel):\n    query: str\n\nclass ShadowMapRequest(BaseModel):\n    target: str\n\nclass DomainOracleRequest(BaseModel):\n    domain: str\n\nclass MailTracerRequest(BaseModel):\n    email: str\n\nclass CodeHunterRequest(BaseModel):\n    username: str\n\nclass WaybackRequest(BaseModel):\n    target: str\n\nclass PasteRequest(BaseModel):\n    query: str\n\nclass AIAnalystRequest(BaseModel):\n    target: str\n    mode: str = \"auto\"\n\napp = FastAPI(title=\"The Big Brother V5 API\")\n\napp.add_middleware(\n    CORSMiddleware,\n    allow_origins=[\"*\"],\n    allow_methods=[\"*\"],\n    allow_headers=[\"*\"],\n)\n\n# In-memory storage\nclass JobState:\n    def __init__(self):\n        self.status = \"running\"\n        self.results = []\n        self.images = []\n        self.image_diag = \"\"\n        self.stop_requested = False\n\njobs: dict[str, JobState] = {}\n\nclass ScanRequest(BaseModel):\n    username: str\n\nclass NotifyQueue(QueryNotify):\n    def __init__(self, job_id, jobs_dict):\n        self.job_id = job_id\n        self.jobs = jobs_dict\n        super().__init__()\n\n    def update(self, result):\n        if self.jobs[self.job_id].stop_requested:\n            raise InterruptedError(\"Stopped by user\")\n\n        if result.status == QueryStatus.CLAIMED:\n            self.jobs[self.job_id].results.append({\n                \"site\": result.site_name,\n                \"url\": result.site_url_user,\n                \"status\": \"Found\",\n                \"validation\": \"Pending\",\n                \"context\": result.context\n            })\n        elif result.status == QueryStatus.WAF:\n             self.jobs[self.job_id].results.append({\n                \"site\": result.site_name,\n                \"url\": result.site_url_user,\n                \"status\": \"WAF Blocked\",\n                \"validation\": \"Pending\",\n                \"context\": result.context\n            })\n\n    def start(self, message=None):\n        pass\n    \n    def finish(self, message=None):\n        pass\n\ndef run_scan_job(job_id: str, username: str):\n    try:\n        # Handle spaces: Check \"John Doe\" and \"JohnDoe\" (or replace space with nothing)\n        usernames_to_check = [username]\n        if \" \" in username:\n            usernames_to_check.append(username.replace(\" \", \"\"))\n\n        # 1. Fetch Images (only for the primary username) — capture diagnostics\n        try:\n            images, diag = fetch_images_with_diag(username, limit=6)\n            jobs[job_id].images = images\n            jobs[job_id].image_diag = diag\n        except Exception as e:\n            print(f\"Image fetch error: {e}\")\n            jobs[job_id].image_diag = f\"fatal: {e}\"\n\n        # 2. Run Scan\n        # Use local data.json file to ensure all sites are loaded\n        # Get the path to the local data.json file\n        data_file_path = os.path.join(os.path.dirname(__file__), \"..\", \"resources\", \"data.json\")\n        sites_info = SitesInformation(data_file_path=data_file_path, honor_exclusions=False)\n        site_data = {site.name: site.information for site in sites_info}\n        \n        notify = NotifyQueue(job_id, jobs)\n        \n        try:\n            for u in usernames_to_check:\n                if jobs[job_id].stop_requested: break\n                scan(u, site_data, notify)\n        except InterruptedError:\n            jobs[job_id].status = \"stopped\"\n            return\n\n        if jobs[job_id].stop_requested:\n             jobs[job_id].status = \"stopped\"\n             return\n\n        # 3. Validate\n        jobs[job_id].status = \"validating\"\n        validate_results(job_id)\n        \n        if jobs[job_id].stop_requested:\n            jobs[job_id].status = \"stopped\"\n        else:\n            jobs[job_id].status = \"completed\"\n\n    except Exception as e:\n        import traceback\n        traceback.print_exc()\n        print(f\"Error in scan job: {e}\")\n        jobs[job_id].status = \"error\"\n\ndef validate_results(job_id: str):\n    results = jobs[job_id].results\n    if not results:\n        return\n\n    to_validate = [r for r in results if r[\"status\"] == \"Found\"]\n    \n    if not to_validate:\n        return\n\n    try:\n        with HeadlessValidator(headless=True) as validator:\n            for res in to_validate:\n                if jobs[job_id].stop_requested:\n                    break\n                \n                res[\"validation\"] = \"Checking...\"\n                val_res = validator.validate(res[\"url\"])\n                \n                if val_res.is_profile:\n                    res[\"validation\"] = \"Verified\"\n                    res[\"page_title\"] = val_res.title\n                    res[\"snippet\"] = val_res.visible_text[:200] if val_res.visible_text else \"\"\n                else:\n                    res[\"validation\"] = \"False Positive\"\n                    res[\"reason\"] = val_res.reason\n    except Exception as e:\n        print(f\"Validation error: {e}\")\n\n@app.post(\"/api/scan\")\nasync def start_scan(request: ScanRequest, background_tasks: BackgroundTasks):\n    job_id = str(uuid4())\n    jobs[job_id] = JobState()\n    background_tasks.add_task(run_scan_job, job_id, request.username)\n    return {\"job_id\": job_id}\n\n@app.post(\"/api/stop/{job_id}\")\nasync def stop_scan(job_id: str):\n    if job_id in jobs:\n        jobs[job_id].stop_requested = True\n        return {\"status\": \"stopping\"}\n    return {\"error\": \"Job not found\"}\n\n@app.get(\"/api/results/{job_id}\")\nasync def get_results(job_id: str):\n    if job_id not in jobs:\n        return {\"error\": \"Job not found\"}\n    return {\n        \"status\": jobs[job_id].status,\n        \"results\": jobs[job_id].results,\n        \"images\": jobs[job_id].images,\n        \"image_diag\": jobs[job_id].image_diag,\n    }\n\n@app.get(\"/api/download/{job_id}\")\nasync def download_report(job_id: str):\n    if job_id not in jobs:\n        return {\"error\": \"Job not found\"}\n    \n    results = jobs[job_id].results\n    output = io.StringIO()\n    writer = csv.writer(output)\n    writer.writerow([\"Site\", \"URL\", \"Status\", \"Validation\", \"Page Title\"])\n    \n    for r in results:\n        writer.writerow([\n            r.get(\"site\"), \n            r.get(\"url\"), \n            r.get(\"status\"), \n            r.get(\"validation\"), \n            r.get(\"page_title\", \"\")\n        ])\n    \n    return Response(\n        content=output.getvalue(),\n        media_type=\"text/csv\",\n        headers={\"Content-Disposition\": f\"attachment; filename=report_{job_id}.csv\"}\n    )\n\n@app.post(\"/api/deep-search\")\nasync def deep_search(request: DeepSearchRequest):\n    searcher = ReverseImageSearcher(headless=True)\n    results = await searcher.search(request.image_url)\n    return results\n\n@app.post(\"/api/footprint\")\nasync def footprint_scan(request: FootprintRequest):\n    if request.type == \"phone\":\n        return get_phone_info(request.query)\n    elif request.type == \"email\":\n        return await run_holehe(request.query)\n    elif request.type == \"breach\":\n        # Redirect to new breach vault\n        return await breach_vault_search(request.query, \"email\")\n    return {\"error\": \"Invalid type\"}\n\n@app.post(\"/api/phantom\")\nasync def phantom_scan(request: PhantomRequest):\n    return await phantom_id_search(request.username)\n\n@app.post(\"/api/breach\")\nasync def breach_scan(request: BreachRequest):\n    return await breach_vault_search(request.query, request.type)\n\n@app.post(\"/api/sigint\")\nasync def sigint_scan(request: SigintRequest):\n    return await sigint_sweep(request.query)\n\n@app.post(\"/api/shadowmap\")\nasync def shadowmap_scan(request: ShadowMapRequest):\n    return await shadow_map_analyze(request.target)\n\n@app.post(\"/api/network/scan\")\nasync def network_scan(request: NetworkRequest):\n    data = await scan_target(request.domain)\n    # Generate map HTML\n    if \"error\" not in data:\n         graph_html = generate_network_map(data)\n         data[\"map_html\"] = graph_html\n    return data\n\n@app.post(\"/api/dark/search\")\nasync def dark_search(request: DarkRequest):\n    return await search_dark_web(request.query)\n\n@app.post(\"/api/crypto/analyze\")\nasync def crypto_analyze(request: CryptoRequest):\n    return analyze_crypto(request.address, request.coin)\n\n@app.post(\"/api/ssl/scan\")\nasync def ssl_scan(request: SSLRequest):\n    return get_ssl_info(request.domain)\n\n@app.post(\"/api/tools/exif\")\nasync def tool_exif(request: ExifRequest):\n    return get_exif_data(request.url)\n\n# FILE UPLOAD for EXIF\n@app.post(\"/api/tools/exif/upload\")\nasync def tool_exif_upload(file: UploadFile = File(...)):\n    # Read bytes\n    content = await file.read()\n    # Modify get_exif_data to accept bytes. \n    # Since we can't easily modify the module function signature without breaking it elsewhere or refactoring,\n    # let's duplicate the logic here or update the module.\n    # Actually, let's update the module logic in-place via a helper if possible.\n    # But for now, let's pass a byte stream if the module supports it or just use PIL directly here.\n    \n    from PIL import Image\n    from PIL.ExifTags import TAGS, GPSTAGS\n    from io import BytesIO\n    \n    results = {\"source\": file.filename, \"basic\": {}, \"gps\": {}, \"error\": None}\n    try:\n        image = Image.open(BytesIO(content))\n        results[\"basic\"][\"format\"] = image.format\n        results[\"basic\"][\"mode\"] = image.mode\n        results[\"basic\"][\"size\"] = f\"{image.width}x{image.height}\"\n        \n        exif_data = image._getexif()\n        if exif_data:\n            for tag_id, value in exif_data.items():\n                tag = TAGS.get(tag_id, tag_id)\n                if isinstance(value, bytes):\n                    try: value = value.decode()\n                    except: value = str(value)\n\n                if tag == \"GPSInfo\":\n                    gps_data = {}\n                    for t in value:\n                        sub_tag = GPSTAGS.get(t, t)\n                        gps_data[sub_tag] = str(value[t])\n                    results[\"gps\"] = gps_data\n                else:\n                    if len(str(value)) < 500:\n                        results[\"basic\"][tag] = value\n    except Exception as e:\n        results[\"error\"] = str(e)\n    return results\n\n@app.post(\"/api/tools/dork\")\nasync def tool_dork(request: DorkRequest):\n    return generate_dorks(request.target, request.domain)\n\n@app.post(\"/api/tools/geoint\")\nasync def tool_geoint(request: GeointRequest):\n    return get_geoint_data(request.lat, request.lon)\n\n@app.post(\"/api/tools/flight\")\nasync def tool_flight(request: FlightRequest):\n    return get_flight_radar(request.lat, request.lon, request.radius)\n\n\n# === V5 endpoints ===\n\n@app.post(\"/api/oracle\")\nasync def oracle_scan(request: DomainOracleRequest):\n    return await domain_oracle(request.domain)\n\n@app.post(\"/api/mailtracer\")\nasync def mail_scan(request: MailTracerRequest):\n    return await mail_tracer(request.email)\n\n@app.post(\"/api/codehunter\")\nasync def code_scan(request: CodeHunterRequest):\n    return await code_hunter(request.username)\n\n@app.post(\"/api/wayback\")\nasync def wayback_scan(request: WaybackRequest):\n    return await wayback_spectre(request.target)\n\n@app.post(\"/api/paste\")\nasync def paste_scan(request: PasteRequest):\n    return await paste_dragnet(request.query)\n\n@app.post(\"/api/analyst\")\nasync def analyst_scan(request: AIAnalystRequest):\n    return await ai_analyst(request.target, request.mode)\n\n\n# Serve static files for frontend\nstatic_dir = os.path.join(os.path.dirname(__file__), \"static\")\nif os.path.exists(static_dir):\n    app.mount(\"/\", StaticFiles(directory=static_dir, html=True), name=\"static\")\n"
  },
  {
    "path": "the_big_brother/gui/static/3.css",
    "content": "/* ============================================================\n   THE BIG BROTHER V5 — EVOLVED CYBERPUNK\n   Premium HUD theme · holographic glass · max-FX motion layer\n   ============================================================ */\n\n@import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Orbitron:wght@400;500;700;800;900&family=JetBrains+Mono:wght@400;600&family=Inter:wght@300;400;500;600;700&display=swap');\n\n/* ---------- design tokens ---------- */\n:root {\n    --bg-0: #030308;\n    --bg-1: #07070f;\n    --bg-2: #0c0c18;\n    --bg-3: #131325;\n    --panel: rgba(10, 10, 22, 0.72);\n    --panel-2: rgba(14, 14, 30, 0.85);\n    --panel-edge: rgba(255, 0, 80, 0.28);\n    --panel-edge-cyan: rgba(0, 240, 255, 0.28);\n\n    --text: #e6e9f5;\n    --text-mute: #8a8fa8;\n    --text-dim: #5a5e75;\n\n    --red: #ff0050;\n    --red-bright: #ff3a73;\n    --red-deep: #b8002f;\n    --cyan: #00f0ff;\n    --cyan-bright: #6cfcff;\n    --magenta: #ff00d4;\n    --green: #00ff8c;\n    --amber: #ffb800;\n    --violet: #a259ff;\n\n    --grad-hot:   linear-gradient(135deg, #ff0050 0%, #ff00d4 100%);\n    --grad-cool:  linear-gradient(135deg, #00f0ff 0%, #a259ff 100%);\n    --grad-warn:  linear-gradient(135deg, #ffb800 0%, #ff0050 100%);\n    --grad-ok:    linear-gradient(135deg, #00ff8c 0%, #00f0ff 100%);\n    --grad-holo:  linear-gradient(135deg, #ff0050, #a259ff 25%, #00f0ff 55%, #00ff8c 80%, #ffb800);\n    --grad-panel: linear-gradient(135deg, rgba(255,0,80,0.06), rgba(0,240,255,0.06));\n\n    --shadow-neon: 0 0 24px rgba(255, 0, 80, 0.35), 0 0 60px rgba(255, 0, 80, 0.12);\n    --shadow-cyan: 0 0 24px rgba(0, 240, 255, 0.35), 0 0 60px rgba(0, 240, 255, 0.12);\n    --shadow-panel: 0 14px 40px -16px rgba(0,0,0,0.7), inset 0 1px 0 rgba(255,255,255,0.04);\n\n    --radius: 14px;\n    --radius-sm: 8px;\n    --radius-pill: 999px;\n\n    --transition: 220ms cubic-bezier(0.2, 0.8, 0.2, 1);\n    --transition-slow: 480ms cubic-bezier(0.2, 0.8, 0.2, 1);\n\n    --font-display: 'Orbitron', sans-serif;\n    --font-mono: 'JetBrains Mono', 'Share Tech Mono', monospace;\n    --font-ui: 'Inter', system-ui, sans-serif;\n}\n\n* { box-sizing: border-box; }\n*::selection { background: var(--red); color: #000; }\n\nhtml, body {\n    margin: 0; padding: 0; height: 100%;\n    background: var(--bg-0);\n    color: var(--text);\n    font-family: var(--font-ui);\n    overflow: hidden;\n    -webkit-font-smoothing: antialiased;\n    text-rendering: optimizeLegibility;\n}\n\nbody::before, body::after {\n    content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 0;\n}\nbody::before {\n    background:\n        radial-gradient(60% 50% at 10% 10%, rgba(255, 0, 80, 0.18), transparent 60%),\n        radial-gradient(50% 40% at 90% 20%, rgba(0, 240, 255, 0.15), transparent 60%),\n        radial-gradient(40% 40% at 50% 95%, rgba(162, 89, 255, 0.16), transparent 60%);\n    animation: auroraDrift 22s ease-in-out infinite alternate;\n    filter: blur(20px);\n}\nbody::after {\n    background:\n        linear-gradient(rgba(255, 0, 80, 0.05) 1px, transparent 1px) 0 0 / 48px 48px,\n        linear-gradient(90deg, rgba(0, 240, 255, 0.05) 1px, transparent 1px) 0 0 / 48px 48px;\n    mask-image: radial-gradient(ellipse at center, black 30%, transparent 75%);\n    animation: gridPan 40s linear infinite;\n}\n@keyframes auroraDrift {\n    0%   { transform: translate(0, 0)   scale(1); }\n    100% { transform: translate(-3%, 2%) scale(1.06); }\n}\n@keyframes gridPan {\n    from { background-position: 0 0, 0 0; }\n    to   { background-position: 480px 480px, 480px 480px; }\n}\n\n.scanlines {\n    position: fixed; inset: 0; pointer-events: none; z-index: 1;\n    background: repeating-linear-gradient(to bottom, rgba(255,255,255,0.012) 0 1px, transparent 1px 3px);\n    mix-blend-mode: overlay;\n}\n.vignette {\n    position: fixed; inset: 0; pointer-events: none; z-index: 2;\n    box-shadow: inset 0 0 220px rgba(0, 0, 0, 0.85);\n}\n#particle-canvas {\n    position: fixed; inset: 0; pointer-events: none; z-index: 1; opacity: 0.55;\n}\n\n.app-container {\n    position: relative; z-index: 5;\n    display: grid;\n    grid-template-columns: 280px 1fr;\n    width: 100%; height: 100vh;\n}\n\n.sidebar {\n    background: linear-gradient(180deg, rgba(8,8,18,0.92), rgba(8,8,18,0.78));\n    backdrop-filter: blur(18px) saturate(140%);\n    -webkit-backdrop-filter: blur(18px) saturate(140%);\n    border-right: 1px solid var(--panel-edge);\n    display: flex; flex-direction: column; overflow: hidden;\n    position: relative;\n}\n.sidebar::before {\n    content: ''; position: absolute; top: 0; right: -1px;\n    width: 1px; height: 100%;\n    background: linear-gradient(180deg, transparent, var(--red), var(--cyan), transparent);\n    opacity: 0.7; animation: edgePulse 6s ease-in-out infinite;\n}\n@keyframes edgePulse {\n    0%, 100% { opacity: 0.35; } 50% { opacity: 0.95; }\n}\n\n.sidebar-header {\n    padding: 26px 20px 22px;\n    border-bottom: 1px solid var(--panel-edge);\n    text-align: center; position: relative;\n}\n\n.brand-mark {\n    width: 64px; height: 64px; margin: 0 auto 12px;\n    border-radius: 50%;\n    background: var(--grad-holo);\n    background-size: 300% 300%;\n    animation: holoShift 8s linear infinite;\n    position: relative; display: grid; place-items: center;\n    box-shadow: 0 0 24px rgba(255, 0, 80, 0.45), inset 0 0 24px rgba(0, 240, 255, 0.25);\n}\n.brand-mark::after {\n    content: ''; position: absolute; inset: 4px;\n    border-radius: 50%;\n    background: radial-gradient(circle at 50% 35%, #1a1a2e, #050510);\n}\n.brand-mark span {\n    position: relative; font-size: 28px; z-index: 2;\n    filter: drop-shadow(0 0 8px var(--red));\n}\n.brand-mark svg {\n    position: relative;\n    z-index: 2;\n    width: 40px;\n    height: 40px;\n    filter: drop-shadow(0 0 10px rgba(255, 0, 80, 0.7))\n            drop-shadow(0 0 18px rgba(0, 240, 255, 0.35));\n    animation: markFloat 6s ease-in-out infinite;\n}\n@keyframes markFloat {\n    0%, 100% { transform: rotate(0deg) scale(1); }\n    50%      { transform: rotate(2deg) scale(1.04); }\n}\n@keyframes holoShift {\n    0%   { background-position: 0% 50%; }\n    50%  { background-position: 100% 50%; }\n    100% { background-position: 0% 50%; }\n}\n\n.sidebar-header h1 {\n    margin: 0;\n    font-family: var(--font-display);\n    font-weight: 900; font-size: 1.35rem; letter-spacing: 4px;\n    background: var(--grad-hot);\n    -webkit-background-clip: text; background-clip: text;\n    color: transparent; position: relative;\n    text-shadow: 0 0 18px rgba(255, 0, 80, 0.3);\n}\n.sidebar-header h1.glitch::before,\n.sidebar-header h1.glitch::after {\n    content: attr(data-text);\n    position: absolute; top: 0; left: 0; width: 100%; height: 100%;\n    background: var(--grad-hot);\n    -webkit-background-clip: text; background-clip: text;\n    color: transparent; overflow: hidden;\n}\n.sidebar-header h1.glitch::before { animation: glitchTop 2.4s infinite linear alternate-reverse; }\n.sidebar-header h1.glitch::after  { animation: glitchBot 3.2s infinite linear alternate-reverse; }\n@keyframes glitchTop {\n    0%   { transform: translate(0, 0); clip-path: inset(0 0 80% 0); }\n    20%  { transform: translate(-2px, 1px); clip-path: inset(0 0 70% 0); }\n    40%  { transform: translate(1px, -1px); clip-path: inset(10% 0 60% 0); }\n    60%  { transform: translate(-1px, 1px); clip-path: inset(20% 0 50% 0); }\n    100% { transform: translate(0, 0); clip-path: inset(0 0 80% 0); }\n}\n@keyframes glitchBot {\n    0%   { transform: translate(0, 0); clip-path: inset(80% 0 0 0); }\n    25%  { transform: translate(2px, -1px); clip-path: inset(70% 0 0 0); }\n    50%  { transform: translate(-1px, 1px); clip-path: inset(60% 0 10% 0); }\n    75%  { transform: translate(1px, -1px); clip-path: inset(50% 0 20% 0); }\n    100% { transform: translate(0, 0); clip-path: inset(80% 0 0 0); }\n}\n\n.sidebar-header .subtitle {\n    font-family: var(--font-mono); font-size: 0.65rem;\n    color: var(--text-mute); margin-top: 10px;\n    letter-spacing: 3px; text-transform: uppercase;\n}\n\n.status-strip {\n    margin-top: 14px;\n    display: flex; gap: 8px; justify-content: center;\n    font-family: var(--font-mono); font-size: 0.55rem;\n    color: var(--text-mute); letter-spacing: 1.5px;\n}\n.status-dot { display: inline-flex; align-items: center; gap: 5px; }\n.status-dot::before {\n    content: ''; width: 6px; height: 6px; border-radius: 50%;\n    background: var(--green); box-shadow: 0 0 10px var(--green);\n    animation: pulse 1.5s ease-in-out infinite;\n}\n.status-dot.warn::before { background: var(--amber); box-shadow: 0 0 10px var(--amber); }\n@keyframes pulse {\n    0%, 100% { opacity: 1; transform: scale(1); }\n    50%      { opacity: 0.4; transform: scale(0.85); }\n}\n\n.nav-section-title {\n    padding: 18px 16px 8px;\n    font-family: var(--font-mono); font-size: 0.6rem;\n    color: var(--text-dim);\n    letter-spacing: 2px; text-transform: uppercase;\n    display: flex; align-items: center; gap: 8px;\n}\n.nav-section-title::after {\n    content: ''; flex: 1; height: 1px;\n    background: linear-gradient(90deg, var(--panel-edge), transparent);\n}\n\n.sidebar-nav {\n    flex: 1; overflow-y: auto;\n    padding: 6px 10px 16px;\n    display: flex; flex-direction: column; gap: 3px;\n    scrollbar-width: thin; scrollbar-color: var(--red) transparent;\n}\n.sidebar-nav::-webkit-scrollbar { width: 4px; }\n.sidebar-nav::-webkit-scrollbar-thumb {\n    background: linear-gradient(180deg, var(--red), var(--cyan));\n    border-radius: 4px;\n}\n\n.tab-btn {\n    display: flex; align-items: center; gap: 12px;\n    padding: 11px 14px;\n    background: transparent;\n    border: 1px solid transparent; border-left: 2px solid transparent;\n    border-radius: var(--radius-sm);\n    color: var(--text-mute);\n    font-family: var(--font-ui);\n    font-size: 0.78rem; font-weight: 500; letter-spacing: 1.2px;\n    text-align: left; cursor: pointer;\n    transition: var(--transition);\n    position: relative; overflow: hidden;\n}\n.tab-btn > span:first-child {\n    font-size: 1.05rem; width: 22px; text-align: center;\n    filter: grayscale(0.4); transition: var(--transition);\n}\n.tab-btn:hover {\n    color: var(--text);\n    border-color: var(--panel-edge);\n    background: rgba(255, 0, 80, 0.05);\n    transform: translateX(2px);\n}\n.tab-btn:hover > span:first-child { filter: grayscale(0) drop-shadow(0 0 4px var(--red)); }\n.tab-btn.active {\n    color: #fff; border-left-color: var(--red);\n    background: linear-gradient(90deg, rgba(255, 0, 80, 0.18), rgba(255, 0, 80, 0.02) 80%);\n    box-shadow: inset 0 0 18px rgba(255, 0, 80, 0.08);\n}\n.tab-btn.active > span:first-child { filter: grayscale(0) drop-shadow(0 0 6px var(--red)); }\n.tab-btn.active::after {\n    content: ''; position: absolute; right: 10px; top: 50%;\n    width: 4px; height: 4px;\n    background: var(--red); border-radius: 50%;\n    box-shadow: 0 0 8px var(--red);\n    transform: translateY(-50%); animation: pulse 1.4s ease-in-out infinite;\n}\n.tab-btn .new-badge {\n    margin-left: auto;\n    padding: 2px 6px;\n    font-family: var(--font-mono); font-size: 0.55rem; font-weight: 700; letter-spacing: 1px;\n    background: var(--grad-cool); color: #000;\n    border-radius: var(--radius-pill);\n    box-shadow: 0 0 12px rgba(0, 240, 255, 0.5);\n}\n\n.nav-premium-btn {\n    margin: 12px 10px;\n    padding: 13px 16px;\n    background: var(--grad-hot); background-size: 200% 200%;\n    border: 1px solid rgba(255, 255, 255, 0.15);\n    border-radius: var(--radius-sm);\n    color: #fff;\n    font-family: var(--font-display); font-weight: 700; font-size: 0.78rem; letter-spacing: 2px;\n    cursor: pointer;\n    box-shadow: 0 0 24px rgba(255, 0, 80, 0.45);\n    animation: holoShift 5s linear infinite;\n    transition: var(--transition);\n    text-align: center;\n    display: flex; align-items: center; justify-content: center; gap: 8px;\n}\n.nav-premium-btn:hover {\n    transform: translateY(-2px) scale(1.02);\n    box-shadow: 0 0 32px rgba(255, 0, 80, 0.7);\n}\n\n.main-content {\n    display: flex; flex-direction: column;\n    overflow: hidden; min-width: 0;\n}\n\n.topbar {\n    height: 72px;\n    background: var(--panel);\n    backdrop-filter: blur(16px) saturate(140%);\n    -webkit-backdrop-filter: blur(16px) saturate(140%);\n    border-bottom: 1px solid var(--panel-edge);\n    display: flex; align-items: center; padding: 0 28px;\n    justify-content: space-between;\n    position: relative;\n}\n.topbar::after {\n    content: ''; position: absolute; bottom: -1px; left: 0;\n    height: 1px; width: 100%;\n    background: linear-gradient(90deg, transparent, var(--red), var(--cyan), transparent);\n    opacity: 0.6;\n}\n\n.topbar-title {\n    display: flex; align-items: center; gap: 14px;\n    font-family: var(--font-display);\n    font-weight: 800; font-size: 1.05rem; letter-spacing: 3px;\n    color: #fff;\n}\n.topbar-title .crosshair {\n    width: 22px; height: 22px;\n    border: 1.5px solid var(--red); border-radius: 50%;\n    position: relative; animation: spin 8s linear infinite;\n}\n.topbar-title .crosshair::before,\n.topbar-title .crosshair::after {\n    content: ''; position: absolute; background: var(--red);\n}\n.topbar-title .crosshair::before { top: 50%; left: -4px; right: -4px; height: 1px; transform: translateY(-50%); }\n.topbar-title .crosshair::after  { left: 50%; top: -4px; bottom: -4px; width: 1px; transform: translateX(-50%); }\n@keyframes spin { to { transform: rotate(360deg); } }\n\n.topbar-meta {\n    display: flex; gap: 22px;\n    font-family: var(--font-mono);\n    font-size: 0.7rem; color: var(--text-mute);\n    letter-spacing: 1.5px;\n}\n.topbar-meta strong { color: var(--cyan); font-weight: 500; }\n.topbar-meta .live-dot {\n    display: inline-block; width: 6px; height: 6px;\n    border-radius: 50%;\n    background: var(--green); box-shadow: 0 0 8px var(--green);\n    margin-right: 6px; animation: pulse 1.2s ease-in-out infinite;\n}\n\n.content-area {\n    flex: 1; overflow-y: auto; padding: 28px;\n    scroll-behavior: smooth;\n    scrollbar-width: thin; scrollbar-color: var(--red) transparent;\n}\n.content-area::-webkit-scrollbar { width: 8px; }\n.content-area::-webkit-scrollbar-track { background: transparent; }\n.content-area::-webkit-scrollbar-thumb {\n    background: linear-gradient(180deg, var(--red), var(--cyan));\n    border-radius: 4px;\n}\n\n.tab-content { display: none; animation: tabEnter 480ms cubic-bezier(0.2, 0.8, 0.2, 1); }\n.tab-content.active { display: block; }\n@keyframes tabEnter {\n    0%   { opacity: 0; transform: translateY(8px) scale(0.99); filter: blur(4px); }\n    100% { opacity: 1; transform: translateY(0)    scale(1);    filter: blur(0); }\n}\n\n.hud-panel {\n    background: var(--panel);\n    backdrop-filter: blur(18px) saturate(140%);\n    -webkit-backdrop-filter: blur(18px) saturate(140%);\n    border: 1px solid var(--panel-edge);\n    box-shadow: var(--shadow-panel);\n    padding: 22px; margin-bottom: 22px;\n    border-radius: var(--radius);\n    position: relative; overflow: hidden;\n    transition: var(--transition);\n}\n.hud-panel::before {\n    content: ''; position: absolute; inset: 0;\n    background: var(--grad-panel); opacity: 0.6; pointer-events: none;\n}\n.hud-panel:hover {\n    border-color: rgba(255, 255, 255, 0.15);\n    transform: translateY(-2px);\n    box-shadow: 0 22px 50px -20px rgba(255, 0, 80, 0.35), var(--shadow-panel);\n}\n.hud-panel .bracket {\n    position: absolute; width: 14px; height: 14px;\n    border: 1.5px solid var(--red);\n    pointer-events: none; opacity: 0.6;\n}\n.hud-panel .bracket.tl { top: 6px; left: 6px; border-right: 0; border-bottom: 0; }\n.hud-panel .bracket.tr { top: 6px; right: 6px; border-left: 0; border-bottom: 0; }\n.hud-panel .bracket.bl { bottom: 6px; left: 6px; border-right: 0; border-top: 0; }\n.hud-panel .bracket.br { bottom: 6px; right: 6px; border-left: 0; border-top: 0; }\n\n.panel-title {\n    font-family: var(--font-display);\n    font-weight: 700; font-size: 0.9rem; letter-spacing: 2px;\n    color: var(--text); margin: 0 0 16px;\n    display: flex; align-items: center; gap: 10px;\n    text-transform: uppercase;\n}\n.panel-title::before {\n    content: ''; width: 3px; height: 14px;\n    background: var(--grad-hot); border-radius: 2px;\n    box-shadow: 0 0 10px var(--red);\n}\n\n.search-area {\n    display: flex; gap: 10px; margin-bottom: 18px;\n    flex-wrap: wrap; align-items: stretch;\n}\n\ninput, select, textarea {\n    flex: 1; min-width: 180px;\n    padding: 13px 18px;\n    background: rgba(5, 5, 14, 0.7);\n    border: 1px solid var(--panel-edge);\n    border-radius: var(--radius-sm);\n    color: var(--text);\n    font-family: var(--font-mono);\n    font-size: 0.88rem; letter-spacing: 0.5px;\n    outline: none; transition: var(--transition);\n}\ninput::placeholder { color: var(--text-dim); font-family: var(--font-mono); letter-spacing: 1px; }\ninput:focus, select:focus, textarea:focus {\n    border-color: var(--red);\n    background: rgba(5, 5, 14, 0.95);\n    box-shadow: 0 0 0 2px rgba(255, 0, 80, 0.15), 0 0 24px rgba(255, 0, 80, 0.25);\n}\n\n.glitch-btn {\n    padding: 13px 26px;\n    background: transparent;\n    color: var(--cyan);\n    border: 1px solid var(--cyan);\n    border-radius: var(--radius-sm);\n    font-family: var(--font-display);\n    font-weight: 700; font-size: 0.85rem; letter-spacing: 2px;\n    cursor: pointer; position: relative; overflow: hidden;\n    transition: var(--transition); text-transform: uppercase;\n    box-shadow: inset 0 0 14px rgba(0, 240, 255, 0.1);\n    z-index: 1;\n}\n.glitch-btn::before {\n    content: ''; position: absolute; inset: 0;\n    background: var(--grad-cool);\n    transform: scaleX(0); transform-origin: left;\n    transition: var(--transition); z-index: -1;\n}\n.glitch-btn:hover::before { transform: scaleX(1); }\n.glitch-btn:hover {\n    color: #000; border-color: transparent;\n    box-shadow: 0 0 28px rgba(0, 240, 255, 0.55), 0 0 8px rgba(0, 240, 255, 0.35);\n    transform: translateY(-1px);\n}\n.glitch-btn:active { transform: translateY(0); }\n.glitch-btn.hot {\n    color: var(--red); border-color: var(--red);\n    box-shadow: inset 0 0 14px rgba(255, 0, 80, 0.1);\n}\n.glitch-btn.hot::before { background: var(--grad-hot); }\n.glitch-btn.hot:hover { box-shadow: 0 0 28px rgba(255, 0, 80, 0.55); }\n.glitch-btn.ok { color: var(--green); border-color: var(--green); }\n.glitch-btn.ok::before { background: var(--grad-ok); }\n.glitch-btn:disabled { opacity: 0.35; cursor: not-allowed; pointer-events: none; }\n\n.ctrl-btn {\n    background: rgba(5, 5, 14, 0.6);\n    color: var(--text);\n    border: 1px solid var(--panel-edge);\n    padding: 10px 18px; border-radius: var(--radius-sm);\n    cursor: pointer;\n    font-family: var(--font-mono);\n    font-size: 0.78rem; letter-spacing: 1.5px;\n    transition: var(--transition);\n    height: 44px; text-transform: uppercase;\n}\n.ctrl-btn:hover { background: rgba(255, 0, 80, 0.08); border-color: var(--red); }\n.ctrl-btn.stop {\n    color: var(--red);\n    border-color: rgba(255, 0, 80, 0.45);\n    background: rgba(255, 0, 80, 0.05);\n}\n.ctrl-btn.stop:hover { background: var(--red); color: #fff; box-shadow: 0 0 18px rgba(255, 0, 80, 0.5); }\n.ctrl-btn.download {\n    color: var(--green);\n    border-color: rgba(0, 255, 140, 0.45);\n    background: rgba(0, 255, 140, 0.05);\n}\n.ctrl-btn.download:hover { background: var(--green); color: #000; box-shadow: 0 0 18px rgba(0, 255, 140, 0.5); }\n.ctrl-btn:disabled { opacity: 0.35; cursor: not-allowed; }\n\n.controls {\n    display: flex; gap: 12px; margin-bottom: 18px;\n    flex-wrap: wrap;\n    padding-top: 14px; border-top: 1px solid var(--panel-edge);\n}\n\n.results, .results-grid {\n    display: grid;\n    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));\n    gap: 14px;\n}\n\n.result-card {\n    background: linear-gradient(160deg, rgba(20, 20, 36, 0.85), rgba(8, 8, 18, 0.85));\n    border: 1px solid var(--panel-edge);\n    border-radius: var(--radius);\n    padding: 18px;\n    position: relative; overflow: hidden;\n    transform-style: preserve-3d;\n    transition: var(--transition);\n    will-change: transform;\n}\n.result-card::before {\n    content: ''; position: absolute;\n    top: -1px; left: -1px; right: -1px; height: 2px;\n    background: var(--grad-hot); opacity: 0.7;\n}\n.result-card::after {\n    content: ''; position: absolute; inset: 0;\n    background: radial-gradient(400px circle at var(--mx, 50%) var(--my, 50%), rgba(255, 0, 80, 0.12), transparent 40%);\n    pointer-events: none; opacity: 0; transition: var(--transition);\n}\n.result-card:hover {\n    transform: translateY(-4px);\n    border-color: rgba(255, 255, 255, 0.18);\n    box-shadow: 0 18px 40px -16px rgba(255, 0, 80, 0.35);\n}\n.result-card:hover::after { opacity: 1; }\n\n.result-card .card-title {\n    font-family: var(--font-display);\n    font-weight: 700; font-size: 0.9rem; letter-spacing: 1px;\n    color: var(--text); margin: 0 0 6px;\n}\n.result-card .card-url {\n    font-family: var(--font-mono);\n    font-size: 0.72rem; color: var(--cyan);\n    text-decoration: none; word-break: break-all;\n    display: block; margin-bottom: 6px;\n}\n.result-card .card-url:hover { text-decoration: underline; }\n\n.status-badge {\n    display: inline-flex; align-items: center; gap: 5px;\n    padding: 3px 10px;\n    border: 1px solid currentColor;\n    border-radius: var(--radius-pill);\n    font-family: var(--font-mono);\n    font-size: 0.62rem; letter-spacing: 1.5px;\n    text-transform: uppercase;\n    background: rgba(0, 0, 0, 0.4);\n    color: var(--text-mute);\n}\n.status-badge::before {\n    content: ''; width: 5px; height: 5px; border-radius: 50%;\n    background: currentColor; box-shadow: 0 0 6px currentColor;\n}\n.status-badge.verified { color: var(--green); }\n.status-badge.check    { color: var(--amber); animation: pulse 1.2s ease-in-out infinite; }\n.status-badge.false    { color: var(--red); }\n.status-badge.critical { color: var(--red); }\n.status-badge.high     { color: #ff5e2b; }\n.status-badge.medium   { color: var(--amber); }\n.status-badge.low      { color: var(--green); }\n.status-badge.clean    { color: var(--text-dim); }\n.status-badge.info     { color: var(--cyan); }\n\n[id$=\"-status\"], .status {\n    font-family: var(--font-mono);\n    font-size: 0.78rem; letter-spacing: 1px;\n    color: var(--text-mute);\n    padding: 8px 0; min-height: 24px;\n    display: flex; align-items: center; gap: 8px;\n}\n[id$=\"-status\"]:not(:empty)::before, .status:not(:empty)::before {\n    content: '>'; color: var(--red); font-weight: 700;\n}\n\n.score-shell {\n    display: flex; flex-direction: column; gap: 6px;\n    padding: 14px;\n    border: 1px solid var(--panel-edge);\n    border-radius: var(--radius);\n    background: rgba(5, 5, 14, 0.55);\n    position: relative; overflow: hidden;\n}\n.score-bar {\n    height: 14px;\n    background: rgba(255, 255, 255, 0.04);\n    border-radius: var(--radius-pill);\n    overflow: hidden; position: relative;\n}\n.score-fill {\n    height: 100%;\n    background: var(--grad-warn);\n    background-size: 200% 100%;\n    border-radius: var(--radius-pill);\n    transition: width 1.4s cubic-bezier(0.2, 0.8, 0.2, 1);\n    animation: gaugeShift 4s linear infinite;\n    box-shadow: 0 0 14px currentColor;\n    position: relative;\n}\n.score-fill::after {\n    content: ''; position: absolute; inset: 0;\n    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);\n    animation: scoreShine 2.4s linear infinite;\n}\n@keyframes gaugeShift {\n    0%, 100% { background-position: 0% 0; }\n    50%      { background-position: 100% 0; }\n}\n@keyframes scoreShine {\n    from { transform: translateX(-100%); }\n    to   { transform: translateX(100%); }\n}\n.score-meta {\n    display: flex; justify-content: space-between;\n    font-family: var(--font-mono);\n    font-size: 0.72rem; letter-spacing: 1.5px;\n    color: var(--text-mute);\n}\n.score-meta strong { font-family: var(--font-display); font-size: 1.05rem; color: var(--text); }\n\n.dork-wrapper {\n    display: flex; align-items: center;\n    background: rgba(5, 5, 14, 0.6);\n    border: 1px solid var(--panel-edge);\n    border-radius: var(--radius-sm);\n    overflow: hidden; height: 44px;\n}\n.dork-select {\n    background: transparent; color: var(--text);\n    border: none; padding: 0 14px;\n    font-family: var(--font-mono);\n    font-size: 0.78rem; letter-spacing: 1px;\n    outline: none; cursor: pointer;\n    min-width: auto;\n}\n.dork-select option { background: var(--bg-1); color: var(--text); }\n.dork-btn {\n    background: rgba(0, 240, 255, 0.1);\n    color: var(--cyan); border: none;\n    border-left: 1px solid var(--panel-edge);\n    padding: 0 16px; height: 100%;\n    cursor: pointer;\n    font-family: var(--font-display);\n    font-weight: 700; font-size: 0.74rem; letter-spacing: 1.5px;\n    transition: var(--transition);\n}\n.dork-btn:hover { background: var(--cyan); color: #000; }\n\n.images-container {\n    display: flex; justify-content: center; gap: 18px;\n    margin-bottom: 22px; min-height: 180px;\n    perspective: 1200px; flex-wrap: wrap;\n}\n.image-wrapper {\n    position: relative;\n    width: 190px; height: 190px;\n    border-radius: var(--radius);\n    overflow: hidden;\n    transform-style: preserve-3d;\n    transition: var(--transition-slow);\n    border: 1px solid var(--panel-edge);\n    box-shadow: 0 12px 30px -12px rgba(0, 0, 0, 0.7);\n    cursor: pointer;\n}\n.image-wrapper::after {\n    content: ''; position: absolute; inset: 0;\n    background: linear-gradient(160deg, transparent 40%, rgba(255, 0, 80, 0.18));\n    pointer-events: none;\n}\n.image-wrapper:hover {\n    transform: translateY(-6px) rotateY(8deg) rotateX(-4deg) scale(1.04);\n    border-color: var(--red);\n    box-shadow: 0 24px 48px -12px rgba(255, 0, 80, 0.5);\n    z-index: 10;\n}\n.captured-image {\n    width: 100%; height: 100%;\n    object-fit: cover; background: #000;\n    transition: var(--transition);\n}\n.image-wrapper:hover .captured-image { transform: scale(1.06); filter: contrast(1.1); }\n\n.btn-deep, .dl-btn {\n    position: absolute;\n    background: rgba(0, 0, 0, 0.75);\n    color: var(--cyan);\n    border: 1px solid var(--cyan);\n    padding: 5px 10px;\n    font-family: var(--font-mono);\n    font-size: 0.65rem; letter-spacing: 1px;\n    cursor: pointer;\n    border-radius: var(--radius-sm);\n    opacity: 0; transition: var(--transition);\n}\n.image-wrapper:hover .btn-deep,\n.image-wrapper:hover .dl-btn { opacity: 1; }\n.btn-deep { bottom: 8px; left: 8px; }\n.dl-btn { bottom: 8px; right: 8px; color: var(--green); border-color: var(--green); }\n\n.deepModal {\n    position: fixed; inset: 0;\n    background: rgba(2, 2, 8, 0.92);\n    backdrop-filter: blur(14px);\n    -webkit-backdrop-filter: blur(14px);\n    z-index: 1000;\n    display: none;\n    flex-direction: column;\n    padding: 28px;\n}\n.deepModal.active { display: flex; animation: tabEnter 320ms ease-out; }\n.deepModal h3 {\n    font-family: var(--font-display);\n    letter-spacing: 3px; color: var(--cyan); margin: 0 0 18px;\n}\n.deep-grid {\n    flex: 1; display: grid;\n    grid-template-columns: repeat(4, 1fr); gap: 16px;\n    overflow: hidden;\n}\n.deep-col {\n    background: var(--panel);\n    border: 1px solid var(--panel-edge);\n    border-radius: var(--radius);\n    padding: 14px; overflow-y: auto;\n}\n.deep-col h4 {\n    margin: 0 0 10px;\n    font-family: var(--font-display);\n    font-size: 0.78rem; letter-spacing: 2px;\n    color: var(--red);\n}\n.deep-col img {\n    width: 100%;\n    border-radius: var(--radius-sm);\n    margin-bottom: 8px;\n    border: 1px solid var(--panel-edge);\n    transition: var(--transition);\n}\n.deep-col img:hover { transform: scale(1.02); border-color: var(--cyan); }\n.target-scan {\n    color: var(--green); font-family: var(--font-mono);\n    letter-spacing: 1.5px; animation: pulse 1s ease-in-out infinite;\n}\n\n.reveal {\n    opacity: 0; transform: translateY(14px);\n    transition: opacity 600ms ease, transform 600ms cubic-bezier(0.2, 0.8, 0.2, 1);\n}\n.reveal.visible { opacity: 1; transform: translateY(0); }\n\n.typing::after {\n    content: '_'; color: var(--red);\n    animation: caretBlink 1s steps(2) infinite;\n}\n@keyframes caretBlink { 50% { opacity: 0; } }\n\n#boot {\n    position: fixed; inset: 0;\n    background: #03030a;\n    z-index: 5000;\n    display: flex; flex-direction: column; align-items: center; justify-content: center;\n    gap: 22px;\n    font-family: var(--font-mono); color: var(--green);\n    transition: opacity 600ms ease, visibility 0s linear 600ms;\n}\n#boot.done { opacity: 0; visibility: hidden; }\n#boot .boot-logo {\n    font-family: var(--font-display);\n    font-size: 2.4rem; letter-spacing: 8px;\n    background: var(--grad-hot);\n    -webkit-background-clip: text; background-clip: text; color: transparent;\n    filter: drop-shadow(0 0 18px rgba(255, 0, 80, 0.5));\n    animation: pulse 1.6s ease-in-out infinite;\n}\n#boot .boot-bar {\n    width: min(420px, 60vw); height: 4px;\n    border-radius: var(--radius-pill);\n    background: rgba(255, 255, 255, 0.06); overflow: hidden;\n}\n#boot .boot-bar > div {\n    height: 100%; width: 0;\n    background: var(--grad-cool);\n    animation: bootLoad 1.6s cubic-bezier(0.65, 0, 0.35, 1) forwards;\n}\n@keyframes bootLoad { to { width: 100%; } }\n#boot .boot-lines {\n    font-size: 0.74rem; letter-spacing: 1.5px;\n    color: var(--text-mute); min-height: 110px;\n    text-align: left; width: min(420px, 60vw);\n}\n#boot .boot-lines span { display: block; opacity: 0; }\n#boot .boot-lines span.show { opacity: 1; transition: opacity 200ms ease; }\n#boot .boot-lines .ok  { color: var(--green); }\n#boot .boot-lines .err { color: var(--amber); }\n\n.row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }\n.col { display: flex; flex-direction: column; gap: 10px; }\n.spread { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }\n.mono { font-family: var(--font-mono); }\n.dim { color: var(--text-mute); }\n.bright { color: var(--text); }\n.h-cyan { color: var(--cyan); }\n.h-red { color: var(--red); }\n.h-green { color: var(--green); }\n.h-amber { color: var(--amber); }\n.hide { display: none !important; }\n\n.tag {\n    display: inline-block;\n    padding: 3px 9px;\n    background: rgba(0, 240, 255, 0.08);\n    border: 1px solid rgba(0, 240, 255, 0.3);\n    border-radius: var(--radius-sm);\n    color: var(--cyan);\n    font-family: var(--font-mono);\n    font-size: 0.66rem; letter-spacing: 1px;\n    margin: 2px;\n}\n.tag.hot { background: rgba(255, 0, 80, 0.08); border-color: rgba(255, 0, 80, 0.35); color: var(--red); }\n.tag.ok  { background: rgba(0, 255, 140, 0.08); border-color: rgba(0, 255, 140, 0.35); color: var(--green); }\n.tag.warn{ background: rgba(255, 184, 0, 0.08); border-color: rgba(255, 184, 0, 0.35); color: var(--amber); }\n\n.mini-bar { height: 6px; background: rgba(255,255,255,0.04); border-radius: var(--radius-pill); overflow: hidden; margin-top: 6px; }\n.mini-bar > div { height: 100%; background: var(--grad-cool); border-radius: var(--radius-pill); transition: width 1s ease; }\n\n@media (max-width: 980px) {\n    .app-container { grid-template-columns: 1fr; }\n    .sidebar { position: fixed; top: 0; left: -300px; bottom: 0; width: 280px; z-index: 200; transition: left var(--transition); }\n    .sidebar.open { left: 0; }\n}\n\n.leaflet-popup-content-wrapper {\n    background: var(--panel-2) !important;\n    color: var(--text) !important;\n    border: 1px solid var(--panel-edge) !important;\n    border-radius: var(--radius) !important;\n    font-family: var(--font-mono) !important;\n}\n.leaflet-popup-tip { background: var(--panel-2) !important; }\n.leaflet-container { background: var(--bg-1) !important; }\n"
  },
  {
    "path": "the_big_brother/gui/static/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>THE BIG BROTHER · V5.0 // GOD'S EYE PROTOCOL</title>\n    <link rel=\"stylesheet\" href=\"https://unpkg.com/leaflet@1.9.4/dist/leaflet.css\">\n    <link rel=\"stylesheet\" href=\"3.css?v=7.0.0\">\n</head>\n<body>\n\n<!-- ============================================================\n     BOOT OVERLAY\n     ============================================================ -->\n<div id=\"boot\">\n    <div class=\"boot-logo\">THE BIG BROTHER</div>\n    <div class=\"boot-bar\"><div></div></div>\n    <div class=\"boot-lines\" id=\"boot-lines\"></div>\n</div>\n\n<!-- ============================================================\n     AMBIENT BACKDROP LAYERS\n     ============================================================ -->\n<canvas id=\"particle-canvas\"></canvas>\n<div class=\"scanlines\"></div>\n<div class=\"vignette\"></div>\n\n<!-- ============================================================\n     APP SHELL\n     ============================================================ -->\n<div class=\"app-container\">\n\n    <!-- ===== SIDEBAR ===== -->\n    <aside class=\"sidebar\">\n        <div class=\"sidebar-header\">\n            <div class=\"brand-mark\">\n                <svg viewBox=\"0 0 64 64\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n                    <defs>\n                        <linearGradient id=\"bm-edge\" x1=\"0\" y1=\"0\" x2=\"64\" y2=\"64\" gradientUnits=\"userSpaceOnUse\">\n                            <stop offset=\"0%\"   stop-color=\"#ff0050\"/>\n                            <stop offset=\"55%\"  stop-color=\"#a259ff\"/>\n                            <stop offset=\"100%\" stop-color=\"#00f0ff\"/>\n                        </linearGradient>\n                        <linearGradient id=\"bm-fill\" x1=\"0\" y1=\"0\" x2=\"64\" y2=\"64\" gradientUnits=\"userSpaceOnUse\">\n                            <stop offset=\"0%\"   stop-color=\"#ff0050\" stop-opacity=\"0.95\"/>\n                            <stop offset=\"100%\" stop-color=\"#a259ff\" stop-opacity=\"0.9\"/>\n                        </linearGradient>\n                        <radialGradient id=\"bm-core\" cx=\"0.5\" cy=\"0.5\" r=\"0.5\">\n                            <stop offset=\"0%\"   stop-color=\"#ffffff\" stop-opacity=\"1\"/>\n                            <stop offset=\"60%\"  stop-color=\"#00f0ff\" stop-opacity=\"0.9\"/>\n                            <stop offset=\"100%\" stop-color=\"#00f0ff\" stop-opacity=\"0\"/>\n                        </radialGradient>\n                    </defs>\n                    <!-- crosshair ticks -->\n                    <g stroke=\"url(#bm-edge)\" stroke-width=\"1.6\" stroke-linecap=\"round\">\n                        <path d=\"M32 3 L32 9\"/>\n                        <path d=\"M32 55 L32 61\"/>\n                        <path d=\"M3 32 L9 32\"/>\n                        <path d=\"M55 32 L61 32\"/>\n                    </g>\n                    <!-- outer diamond -->\n                    <path d=\"M32 7 L57 32 L32 57 L7 32 Z\"\n                          fill=\"none\" stroke=\"url(#bm-edge)\" stroke-width=\"1.8\" stroke-linejoin=\"miter\"/>\n                    <!-- inner diamond -->\n                    <path d=\"M32 18 L46 32 L32 46 L18 32 Z\"\n                          fill=\"url(#bm-fill)\" opacity=\"0.92\"/>\n                    <!-- core glow -->\n                    <circle cx=\"32\" cy=\"32\" r=\"12\" fill=\"url(#bm-core)\" opacity=\"0.65\"/>\n                    <!-- inner accent line -->\n                    <path d=\"M22 32 L42 32\" stroke=\"#ffffff\" stroke-width=\"0.8\" opacity=\"0.55\"/>\n                    <!-- center diamond pip -->\n                    <path d=\"M32 28 L36 32 L32 36 L28 32 Z\" fill=\"#ffffff\" opacity=\"0.95\"/>\n                </svg>\n            </div>\n            <h1 class=\"glitch\" data-text=\"BIG BROTHER\">BIG BROTHER</h1>\n            <div class=\"subtitle\">V5 · GOD'S EYE PROTOCOL</div>\n            <div class=\"status-strip\">\n                <span class=\"status-dot\">CORE</span>\n                <span class=\"status-dot\">NEURAL</span>\n                <span class=\"status-dot\">UPLINK</span>\n            </div>\n        </div>\n\n        <nav class=\"sidebar-nav\" id=\"main-nav\">\n\n            <div class=\"nav-section-title\">Synthesis</div>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-analyst', 'AI ANALYST')\">\n                <span>🤖</span> AI ANALYST <span class=\"new-badge\">NEW</span>\n            </button>\n\n            <div class=\"nav-section-title\">Identity</div>\n            <button class=\"tab-btn active\" onclick=\"switchTab('tab-profiler', 'TARGET PROFILER')\">\n                <span>🔍</span> PROFILER\n            </button>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-phantom', 'PHANTOM IDENTITY')\">\n                <span>👻</span> PHANTOM ID\n            </button>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-breach', 'BREACH VAULT')\">\n                <span>🔓</span> BREACH VAULT\n            </button>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-footprint', 'DIGITAL FOOTPRINT')\">\n                <span>👣</span> FOOTPRINT\n            </button>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-mailtracer', 'MAIL TRACER')\">\n                <span>📨</span> MAIL TRACER <span class=\"new-badge\">NEW</span>\n            </button>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-codehunter', 'CODE HUNTER')\">\n                <span>🐙</span> CODE HUNTER <span class=\"new-badge\">NEW</span>\n            </button>\n\n            <div class=\"nav-section-title\">Intel</div>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-sigint', 'SIGINT SWEEP')\">\n                <span>📡</span> SIGINT SWEEP\n            </button>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-shadowmap', 'SHADOW MAP')\">\n                <span>🗺️</span> SHADOW MAP\n            </button>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-paste', 'PASTE DRAGNET')\">\n                <span>📋</span> PASTE DRAGNET <span class=\"new-badge\">NEW</span>\n            </button>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-darkwatch', 'DARK WEB WATCH')\">\n                <span>🧅</span> DARK WEB\n            </button>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-wayback', 'WAYBACK SPECTRE')\">\n                <span>🕰️</span> WAYBACK <span class=\"new-badge\">NEW</span>\n            </button>\n\n            <div class=\"nav-section-title\">Infrastructure</div>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-oracle', 'DOMAIN ORACLE')\">\n                <span>🌐</span> DOMAIN ORACLE <span class=\"new-badge\">NEW</span>\n            </button>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-network', 'NETWORK MAPPER')\">\n                <span>🕸️</span> NET SCAN\n            </button>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-ssl', 'SSL SENTINEL')\">\n                <span>🔒</span> SSL\n            </button>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-crypto', 'CRYPTO ANALYZER')\">\n                <span>🪙</span> CRYPTO\n            </button>\n\n            <div class=\"nav-section-title\">Media / Geo</div>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-exif', 'EXIF X-RAY')\">\n                <span>📸</span> EXIF\n            </button>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-dorks', 'DORK STUDIO')\">\n                <span>🛠️</span> DORKS\n            </button>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-geoint', 'GEOINT SPY')\">\n                <span>🛰️</span> GEOINT\n            </button>\n            <button class=\"tab-btn\" onclick=\"switchTab('tab-flight', 'SKY RADAR')\">\n                <span>✈️</span> SKY RADAR\n            </button>\n        </nav>\n\n        <button class=\"nav-premium-btn\" onclick=\"switchTab('tab-premium', 'PREMIUM CATALOG')\">\n            🔥 PREMIUM CATALOG\n        </button>\n    </aside>\n\n    <!-- ===== MAIN COLUMN ===== -->\n    <main class=\"main-content\">\n        <header class=\"topbar\">\n            <div class=\"topbar-title\">\n                <div class=\"crosshair\"></div>\n                <span id=\"active-module-title\">TARGET PROFILER</span>\n            </div>\n            <div class=\"topbar-meta\">\n                <span><span class=\"live-dot\"></span><strong>LIVE</strong></span>\n                <span>SESSION <strong id=\"session-id\">—</strong></span>\n                <span><strong id=\"clock\">UTC 00:00:00</strong></span>\n            </div>\n        </header>\n\n        <div class=\"content-area\" id=\"content-area\">\n\n            <!-- ============================================================\n                 TAB: AI ANALYST (NEW)\n                 ============================================================ -->\n            <div id=\"tab-analyst\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">AI Analyst · Cross-Module Synthesis</div>\n                    <div class=\"dim mono\" style=\"font-size:0.78rem; margin-bottom:14px;\">\n                        Auto-detects target type (email · domain · IP · username), fans out to every relevant module in parallel, and produces a unified risk profile with a single threat score.\n                    </div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"analyst-input\" placeholder=\"ENTER TARGET (email, domain, ip, or @username)\" onkeydown=\"if(event.key==='Enter') runAnalyst()\">\n                        <select id=\"analyst-mode\" class=\"dork-select\" style=\"min-width:120px;\">\n                            <option value=\"auto\">AUTO</option>\n                            <option value=\"email\">EMAIL</option>\n                            <option value=\"domain\">DOMAIN</option>\n                            <option value=\"ip\">IP</option>\n                            <option value=\"username\">USERNAME</option>\n                        </select>\n                        <button onclick=\"runAnalyst()\" class=\"glitch-btn hot\">EXECUTE</button>\n                    </div>\n                    <div id=\"analyst-status\"></div>\n                </div>\n\n                <div id=\"analyst-summary\" class=\"hide\"></div>\n                <div id=\"analyst-findings\" class=\"hide\"></div>\n                <div id=\"analyst-modules\" class=\"hide\"></div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: TARGET PROFILER\n                 ============================================================ -->\n            <div id=\"tab-profiler\" class=\"tab-content active\">\n                <div id=\"images-panel\" class=\"hud-panel reveal hide\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"spread\" style=\"margin-bottom:14px;\">\n                        <div class=\"panel-title\" style=\"margin:0;\">Biometric Capture · Visual Intel</div>\n                        <span id=\"images-count\" class=\"status-badge info\">0 IMAGES</span>\n                    </div>\n                    <div class=\"dim mono\" style=\"font-size:0.74rem; margin-bottom:14px;\">Auto-pulled from DuckDuckGo · Bing · Google Images · click any thumb for reverse-image search across 4 engines</div>\n                    <div id=\"images-container\" class=\"images-container\"></div>\n                </div>\n\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">Target Profiler</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"username\" placeholder=\"ENTER TARGET IDENTIFIER...\"\n                            onkeydown=\"if(event.key==='Enter') startScan()\">\n                        <button onclick=\"startScan()\" id=\"btn-scan\" class=\"glitch-btn hot\">INITIATE</button>\n                    </div>\n\n                    <div class=\"controls\">\n                        <button id=\"btn-stop\" class=\"ctrl-btn stop\" onclick=\"stopScan()\" disabled>[ X ] ABORT</button>\n                        <button id=\"btn-download\" class=\"ctrl-btn download\" onclick=\"downloadReport()\" disabled>[ ↓ ] EXPORT CSV</button>\n\n                        <div class=\"dork-wrapper\">\n                            <select id=\"dork-select\" class=\"dork-select\">\n                                <option value=\"linkedin\">LINKEDIN</option>\n                                <option value=\"instagram\">INSTAGRAM</option>\n                                <option value=\"twitter\">TWITTER</option>\n                                <option value=\"facebook\">FACEBOOK</option>\n                                <option value=\"tiktok\">TIKTOK</option>\n                                <option value=\"pinterest\">PINTEREST</option>\n                                <option value=\"github\">GITHUB</option>\n                                <option value=\"gitlab\">GITLAB</option>\n                                <option value=\"stackoverflow\">STACKOVERFLOW</option>\n                                <option value=\"pastebin\">PASTEBIN</option>\n                                <option value=\"reddit\">REDDIT</option>\n                                <option value=\"pdf\">FILETYPE: PDF</option>\n                                <option value=\"doc\">FILETYPE: DOC</option>\n                                <option value=\"txt\">FILETYPE: TXT</option>\n                                <option value=\"intext\">INTEXT SCAN</option>\n                                <option value=\"intitle\">INTITLE SCAN</option>\n                                <option value=\"inurl\">INURL SCAN</option>\n                                <option value=\"password\">BREACH CHECK</option>\n                                <option value=\"email\">EMAIL CHECK</option>\n                                <option value=\"phone\">PHONE CHECK</option>\n                            </select>\n                            <button class=\"dork-btn\" onclick=\"executeDork()\">DEPLOY ›</button>\n                        </div>\n                    </div>\n\n                    <div id=\"status\">SYSTEM STANDBY.</div>\n                </div>\n\n                <div id=\"results\" class=\"results\"></div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: PHANTOM ID\n                 ============================================================ -->\n            <div id=\"tab-phantom\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">Phantom Identity</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"phantom-input\" placeholder=\"ENTER TARGET USERNAME\" onkeydown=\"if(event.key==='Enter') scanPhantom()\">\n                        <button onclick=\"scanPhantom()\" class=\"glitch-btn hot\">HUNT</button>\n                    </div>\n                    <div class=\"score-shell\" style=\"margin-top:14px;\">\n                        <div class=\"score-meta\">\n                            <span>EXPOSURE SCORE</span>\n                            <strong id=\"phantom-score-text\">STANDBY</strong>\n                        </div>\n                        <div class=\"score-bar\"><div id=\"phantom-score-bar\" class=\"score-fill\" style=\"width:0%;\"></div></div>\n                    </div>\n                    <div id=\"phantom-status\">WAITING FOR TARGET.</div>\n                </div>\n\n                <div id=\"phantom-results\" class=\"results-grid\" style=\"display:block;\">\n                    <div id=\"phantom-meta\" class=\"hide hud-panel\" style=\"margin-bottom:18px;\">\n                        <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                        <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                        <div class=\"row\" style=\"align-items:center;\">\n                            <img id=\"phantom-avatar\" src=\"\" style=\"width:84px; height:84px; border-radius:50%; border:2px solid var(--cyan); box-shadow:0 0 22px rgba(0,240,255,0.5);\">\n                            <div id=\"phantom-cats\" style=\"display:flex; gap:6px; flex-wrap:wrap;\"></div>\n                        </div>\n                    </div>\n                    <div id=\"phantom-grid\" class=\"results\"></div>\n                </div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: BREACH VAULT\n                 ============================================================ -->\n            <div id=\"tab-breach\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">Breach Vault</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"breach-input\" placeholder=\"ENTER EMAIL OR PASSWORD\" onkeydown=\"if(event.key==='Enter') scanBreach()\">\n                        <select id=\"breach-type\" class=\"dork-select\" style=\"min-width:160px;\">\n                            <option value=\"email\">EMAIL</option>\n                            <option value=\"password\">PASSWORD (HASHED)</option>\n                        </select>\n                        <button onclick=\"scanBreach()\" class=\"glitch-btn hot\">AUDIT</button>\n                    </div>\n                    <div id=\"breach-status\">READY TO CHECK LEAKS.</div>\n                </div>\n\n                <div id=\"breach-summary\" style=\"margin-bottom:18px;\"></div>\n                <div id=\"breach-results\" class=\"results\"></div>\n                <div id=\"paste-dump-results\" style=\"margin-top:18px; display:grid; gap:10px;\"></div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: FOOTPRINT\n                 ============================================================ -->\n            <div id=\"tab-footprint\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">Digital Footprint</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"footprint-input\" placeholder=\"ENTER EMAIL OR PHONE (e.g. +1415...)\">\n                        <select id=\"footprint-type\" class=\"dork-select\" style=\"min-width:140px;\">\n                            <option value=\"email\">EMAIL</option>\n                            <option value=\"phone\">PHONE</option>\n                        </select>\n                        <button onclick=\"scanFootprint()\" class=\"glitch-btn\">TRACK</button>\n                    </div>\n                    <div id=\"footprint-status\">READY TO TRACK.</div>\n                </div>\n                <div id=\"footprint-results\" class=\"results-grid\" style=\"display:block;\"></div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: MAIL TRACER (NEW)\n                 ============================================================ -->\n            <div id=\"tab-mailtracer\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">Mail Tracer · Email Forensics</div>\n                    <div class=\"dim mono\" style=\"font-size:0.76rem; margin-bottom:12px;\">MX validity · SPF/DMARC posture · disposable/role detection · gravatar lookup · trust score</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"mail-input\" placeholder=\"ENTER EMAIL (e.g. someone@example.com)\" onkeydown=\"if(event.key==='Enter') scanMail()\">\n                        <button onclick=\"scanMail()\" class=\"glitch-btn\">TRACE</button>\n                    </div>\n                    <div id=\"mail-status\">AWAITING TARGET.</div>\n                </div>\n                <div id=\"mail-results\"></div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: CODE HUNTER (NEW)\n                 ============================================================ -->\n            <div id=\"tab-codehunter\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">Code Hunter · GitHub OSINT</div>\n                    <div class=\"dim mono\" style=\"font-size:0.76rem; margin-bottom:12px;\">Profile · repos · commit-email harvesting · language stats · activity heatmap</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"code-input\" placeholder=\"ENTER GITHUB USERNAME (without @)\" onkeydown=\"if(event.key==='Enter') scanCode()\">\n                        <button onclick=\"scanCode()\" class=\"glitch-btn\">HUNT</button>\n                    </div>\n                    <div id=\"code-status\">READY.</div>\n                </div>\n                <div id=\"code-results\"></div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: SIGINT SWEEP\n                 ============================================================ -->\n            <div id=\"tab-sigint\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">Sigint Sweep</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"sigint-input\" placeholder=\"ENTER BRAND, PERSON, OR KEYWORD\" onkeydown=\"if(event.key==='Enter') scanSigint()\">\n                        <button onclick=\"scanSigint()\" class=\"glitch-btn\">SWEEP</button>\n                    </div>\n                    <div class=\"row\" id=\"sigint-sentiment\" style=\"margin-top:12px; padding:12px; background:rgba(0,0,0,0.35); border-radius:8px; gap:24px; justify-content:center;\">\n                        <div class=\"h-green mono\">▲ POSITIVE: <span id=\"sig-pos\">0</span></div>\n                        <div class=\"h-amber mono\">● NEUTRAL: <span id=\"sig-neu\">0</span></div>\n                        <div class=\"h-red mono\">▼ NEGATIVE: <span id=\"sig-neg\">0</span></div>\n                    </div>\n                    <div id=\"sigint-status\">AWAITING DIRECTIVES.</div>\n                </div>\n                <div id=\"sigint-results\" style=\"display:flex; flex-direction:column; gap:12px;\"></div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: SHADOW MAP\n                 ============================================================ -->\n            <div id=\"tab-shadowmap\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">Shadow Map</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"shadow-input\" placeholder=\"ENTER IP OR DOMAIN TO INVESTIGATE\" onkeydown=\"if(event.key==='Enter') scanShadow()\">\n                        <button onclick=\"scanShadow()\" class=\"glitch-btn hot\">TRACE</button>\n                    </div>\n                    <div id=\"shadow-status\">REPUTATION ENGINE NOMINAL.</div>\n                </div>\n\n                <div id=\"shadow-dashboard\" class=\"hide\" style=\"grid-template-columns: 1fr 2fr; gap:16px;\">\n                    <div class=\"result-card\" style=\"text-align:center; display:flex; flex-direction:column; align-items:center; justify-content:center;\">\n                        <div class=\"dim mono\" style=\"margin-bottom:14px;\">THREAT SCORE</div>\n                        <div id=\"shadow-score\" style=\"font-size:4.5rem; font-weight:900; line-height:1; font-family:var(--font-display);\">0</div>\n                        <div id=\"shadow-level\" class=\"status-badge\" style=\"margin-top:12px; font-size:0.8rem; padding:6px 14px;\">CLEAN</div>\n                        <div id=\"shadow-factors\" class=\"col\" style=\"margin-top:18px; width:100%; text-align:left; font-size:0.78rem;\"></div>\n                    </div>\n                    <div class=\"col\">\n                        <div class=\"result-card\" id=\"shadow-abuse\"></div>\n                        <div class=\"result-card\" id=\"shadow-vt\"></div>\n                        <div class=\"result-card\" id=\"shadow-geo\"></div>\n                    </div>\n                </div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: PASTE DRAGNET (NEW)\n                 ============================================================ -->\n            <div id=\"tab-paste\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">Paste Dragnet · Multi-Site Hunter</div>\n                    <div class=\"dim mono\" style=\"font-size:0.76rem; margin-bottom:12px;\">Pastebin · Ghostbin · Rentry · Paste.ee · 0bin · JustPaste · Hastebin · Ideone · DPaste — via DuckDuckGo dorks</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"paste-input\" placeholder=\"ENTER QUERY (email · domain · keyword)\" onkeydown=\"if(event.key==='Enter') scanPaste()\">\n                        <button onclick=\"scanPaste()\" class=\"glitch-btn hot\">DRAGNET</button>\n                    </div>\n                    <div id=\"paste-status\">READY TO HUNT.</div>\n                </div>\n                <div id=\"paste-summary\"></div>\n                <div id=\"paste-results\" class=\"results\"></div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: DARK WEB\n                 ============================================================ -->\n            <div id=\"tab-darkwatch\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">Dark Web Watch</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"dark-input\" placeholder=\"ENTER KEYWORD (e.g. leak, database)\">\n                        <button onclick=\"scanDarkWeb()\" class=\"glitch-btn hot\">SEARCH ONION</button>\n                    </div>\n                    <div id=\"dark-status\">CAUTION: ONION NETWORK GATEWAY.</div>\n                </div>\n                <div id=\"dark-results\" class=\"results\"></div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: WAYBACK SPECTRE (NEW)\n                 ============================================================ -->\n            <div id=\"tab-wayback\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">Wayback Spectre · Time-Machine Recon</div>\n                    <div class=\"dim mono\" style=\"font-size:0.76rem; margin-bottom:12px;\">Wayback Machine CDX timeline · year buckets · sensitive-path flagging (admin · .env · .git · backups)</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"wayback-input\" placeholder=\"ENTER DOMAIN OR URL\" onkeydown=\"if(event.key==='Enter') scanWayback()\">\n                        <button onclick=\"scanWayback()\" class=\"glitch-btn\">RECALL</button>\n                    </div>\n                    <div id=\"wayback-status\">READY.</div>\n                </div>\n                <div id=\"wayback-results\"></div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: DOMAIN ORACLE (NEW)\n                 ============================================================ -->\n            <div id=\"tab-oracle\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">Domain Oracle · Deep Intel</div>\n                    <div class=\"dim mono\" style=\"font-size:0.76rem; margin-bottom:12px;\">WHOIS (RDAP) · full DNS · SPF/DMARC/DKIM grade · HTTP security headers · TLS · crt.sh subs · reverse-IP</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"oracle-input\" placeholder=\"ENTER DOMAIN (e.g. example.com)\" onkeydown=\"if(event.key==='Enter') scanOracle()\">\n                        <button onclick=\"scanOracle()\" class=\"glitch-btn hot\">DIVINE</button>\n                    </div>\n                    <div id=\"oracle-status\">READY.</div>\n                </div>\n                <div id=\"oracle-results\"></div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: NETWORK MAPPER\n                 ============================================================ -->\n            <div id=\"tab-network\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">Network Mapper</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"network-input\" placeholder=\"ENTER DOMAIN (e.g. example.com)\">\n                        <button onclick=\"scanNetwork()\" class=\"glitch-btn\">MAP NETWORK</button>\n                    </div>\n                    <div id=\"network-status\">READY TO SCAN.</div>\n                </div>\n                <div id=\"network-results\" style=\"display:block; min-height:500px;\">\n                    <div id=\"network-graph\" style=\"width:100%; height:500px; border:1px solid var(--panel-edge); border-radius:var(--radius); background:#000; overflow:hidden;\"></div>\n                    <div id=\"network-ports\" style=\"margin-top:18px;\"></div>\n                </div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: SSL SENTINEL\n                 ============================================================ -->\n            <div id=\"tab-ssl\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">SSL Sentinel</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"ssl-input\" placeholder=\"ENTER DOMAIN (e.g. secure.com)\">\n                        <button onclick=\"scanSSL()\" class=\"glitch-btn\">INSPECT SSL</button>\n                    </div>\n                    <div id=\"ssl-status\">TLS HANDSHAKE PROTOCOL READY.</div>\n                </div>\n                <div id=\"ssl-results\"></div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: CRYPTO\n                 ============================================================ -->\n            <div id=\"tab-crypto\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">Crypto Analyzer</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"crypto-input\" placeholder=\"ENTER WALLET ADDRESS\">\n                        <select id=\"crypto-type\" class=\"dork-select\" style=\"min-width:140px;\">\n                            <option value=\"btc\">BITCOIN</option>\n                            <option value=\"eth\">ETHEREUM</option>\n                        </select>\n                        <button onclick=\"scanCrypto()\" class=\"glitch-btn\">ANALYZE</button>\n                    </div>\n                    <div id=\"crypto-status\">LEDGER CONNECTION ESTABLISHED.</div>\n                </div>\n                <div id=\"crypto-results\"></div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: EXIF\n                 ============================================================ -->\n            <div id=\"tab-exif\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">EXIF X-Ray</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"exif-input\" placeholder=\"ENTER IMAGE URL (http://...)\">\n                        <span class=\"mono dim\" style=\"align-self:center;\">OR</span>\n                        <input type=\"file\" id=\"exif-file\" style=\"min-width:200px;\">\n                    </div>\n                    <div class=\"row\" style=\"margin-top:12px;\">\n                        <button onclick=\"analyzeExif()\" class=\"glitch-btn\">EXTRACT (URL)</button>\n                        <button onclick=\"analyzeExifFile()\" class=\"glitch-btn ok\">EXTRACT (FILE)</button>\n                    </div>\n                    <div id=\"exif-status\">WAITING FOR VISUAL DATA.</div>\n                </div>\n                <div id=\"exif-results\"></div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: DORKS\n                 ============================================================ -->\n            <div id=\"tab-dorks\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">Dork Studio</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"dork-target\" placeholder=\"TARGET KEYWORD (e.g. Tesla)\">\n                        <input type=\"text\" id=\"dork-domain\" placeholder=\"DOMAIN (Optional)\">\n                        <button onclick=\"generateDorks()\" class=\"glitch-btn\">GENERATE</button>\n                    </div>\n                    <div id=\"dork-status\">HACKING QUERIES READY.</div>\n                </div>\n                <div id=\"dork-results\" class=\"results\" style=\"grid-template-columns: 1fr;\"></div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: GEOINT\n                 ============================================================ -->\n            <div id=\"tab-geoint\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <div class=\"panel-title\">Geoint Spy</div>\n                    <div class=\"search-area\">\n                        <input type=\"text\" id=\"geo-lat\" placeholder=\"LATITUDE (e.g. 40.7128)\">\n                        <input type=\"text\" id=\"geo-lon\" placeholder=\"LONGITUDE (e.g. -74.0060)\">\n                        <button onclick=\"runGeoint()\" class=\"glitch-btn ok\">SAT RECON</button>\n                    </div>\n                    <div id=\"geo-status\">SATELLITE UPLINK STANDBY.</div>\n                </div>\n                <div id=\"geo-results\"></div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: SKY RADAR\n                 ============================================================ -->\n            <div id=\"tab-flight\" class=\"tab-content\" style=\"height: calc(100vh - 160px); min-height:600px;\">\n                <div style=\"display:flex; flex-direction:column; height:100%;\">\n                    <div class=\"hud-panel\" style=\"margin-bottom:10px; flex-shrink:0;\">\n                        <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                        <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                        <div class=\"panel-title\">Sky Radar</div>\n                        <div class=\"search-area\" style=\"flex-wrap: wrap; gap:8px;\">\n                            <select id=\"sky-country\" class=\"dork-select\" onchange=\"updateSkyRegion()\" style=\"min-width:160px;\">\n                                <option value=\"\">SELECT REGION...</option>\n                                <option value=\"uk\">UNITED KINGDOM</option>\n                                <option value=\"usa_east\">USA (EAST)</option>\n                                <option value=\"usa_west\">USA (WEST)</option>\n                                <option value=\"france\">FRANCE</option>\n                                <option value=\"germany\">GERMANY</option>\n                                <option value=\"russia\">RUSSIA (MOSCOW)</option>\n                                <option value=\"china\">CHINA (EAST)</option>\n                                <option value=\"japan\">JAPAN</option>\n                                <option value=\"dubai\">UAE (DUBAI)</option>\n                            </select>\n                            <input type=\"text\" id=\"sky-lat\" placeholder=\"LAT\" style=\"max-width:120px;\">\n                            <input type=\"text\" id=\"sky-lon\" placeholder=\"LON\" style=\"max-width:120px;\">\n                            <input type=\"text\" id=\"sky-rad\" value=\"100\" placeholder=\"RAD (KM)\" style=\"max-width:120px;\">\n                            <button onclick=\"scanSky()\" class=\"glitch-btn\">RADAR SCAN</button>\n                            <button onclick=\"if(navigator.geolocation) navigator.geolocation.getCurrentPosition(p=>{document.getElementById('sky-lat').value=p.coords.latitude; document.getElementById('sky-lon').value=p.coords.longitude;})\" class=\"ctrl-btn\">📍 MY LOC</button>\n                        </div>\n                        <div id=\"sky-status\">OPENSKY NETWORK LINKED.</div>\n                    </div>\n\n                    <div style=\"display:flex; gap:14px; flex:1; min-height:0;\">\n                        <div id=\"sky-map\" style=\"flex:3; height:100%; background:var(--bg-1); border:1px solid var(--panel-edge); border-radius:var(--radius); overflow:hidden;\"></div>\n                        <div style=\"flex:1; height:100%; overflow-y:auto; border:1px solid var(--panel-edge); border-radius:var(--radius); background:var(--panel); padding:14px;\">\n                            <div class=\"panel-title\" style=\"margin-bottom:10px;\">LIVE TRAFFIC</div>\n                            <div id=\"sky-results\">\n                                <div class=\"dim mono\" style=\"text-align:center;\">NO DATA FEED</div>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n\n            <!-- ============================================================\n                 TAB: PREMIUM\n                 ============================================================ -->\n            <div id=\"tab-premium\" class=\"tab-content\">\n                <div class=\"hud-panel reveal\" style=\"text-align:center;\">\n                    <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n                    <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n                    <h2 class=\"panel-title\" style=\"justify-content:center; font-size:1.1rem;\">PREMIUM PROJECT CATALOG</h2>\n                    <div class=\"dim mono\" style=\"font-size:0.84rem;\">UNLOCK ADVANCED CAPABILITIES WITH THESE EXCLUSIVE MODULES.</div>\n                </div>\n\n                <div class=\"results\" style=\"grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\">\n                    <div class=\"result-card\">\n                        <div class=\"spread\" style=\"margin-bottom:10px;\">\n                            <h3 class=\"card-title\">TheBigBrother God-EYE</h3>\n                            <span class=\"status-badge critical\">PREMIUM</span>\n                        </div>\n                        <div class=\"dim\" style=\"font-size:0.86rem; margin-bottom:18px;\">The ultimate evolution of OSINT operations. Direct access to proprietary servers and global data APIs for unprecedented deep-level intelligence.</div>\n                        <button class=\"glitch-btn hot\" style=\"width:100%;\" onclick=\"window.open('https://t.me/hisoka0morow', '_blank')\">GET IT</button>\n                    </div>\n                    <div class=\"result-card\">\n                        <div class=\"spread\" style=\"margin-bottom:10px;\">\n                            <h3 class=\"card-title\">X GodMode</h3>\n                            <span class=\"status-badge info\">AUTOMATION</span>\n                        </div>\n                        <div class=\"dim\" style=\"font-size:0.86rem; margin-bottom:18px;\">Deploy fully automated farms across the X ecosystem — auto-publish, RT, like, comment, engineer sentiment.</div>\n                        <button class=\"glitch-btn\" style=\"width:100%;\" onclick=\"window.open('https://t.me/hisoka0morow', '_blank')\">GET IT</button>\n                    </div>\n                    <div class=\"result-card\">\n                        <div class=\"spread\" style=\"margin-bottom:10px;\">\n                            <h3 class=\"card-title\">Amplifior</h3>\n                            <span class=\"status-badge medium\">TIKTOK BOT</span>\n                        </div>\n                        <div class=\"dim\" style=\"font-size:0.86rem; margin-bottom:18px;\">Hands-free TikTok empire builder. Autonomous download → edit → publish loops for explosive organic growth.</div>\n                        <button class=\"glitch-btn ok\" style=\"width:100%;\" onclick=\"window.open('https://t.me/hisoka0morow', '_blank')\">GET IT</button>\n                    </div>\n                    <div class=\"result-card\">\n                        <div class=\"spread\" style=\"margin-bottom:10px;\">\n                            <h3 class=\"card-title\">Psychopath</h3>\n                            <span class=\"status-badge low\">PHISHING</span>\n                        </div>\n                        <div class=\"dim\" style=\"font-size:0.86rem; margin-bottom:18px;\">Definitive engineered phishing platform — 100+ targets, SMTP bridging, conversational AI bots.</div>\n                        <button class=\"glitch-btn ok\" style=\"width:100%;\" onclick=\"window.open('https://t.me/hisoka0morow', '_blank')\">GET IT</button>\n                    </div>\n                    <div class=\"result-card\">\n                        <div class=\"spread\" style=\"margin-bottom:10px;\">\n                            <h3 class=\"card-title\">Custom Project</h3>\n                            <span class=\"status-badge critical\">DEVELOPMENT</span>\n                        </div>\n                        <div class=\"dim\" style=\"font-size:0.86rem; margin-bottom:18px;\">Arsenal of 200+ operational tools. If a capability doesn't exist, we engineer it. Nothing is impossible.</div>\n                        <button class=\"glitch-btn hot\" style=\"width:100%;\" onclick=\"window.open('https://t.me/hisoka0morow', '_blank')\">GET IT</button>\n                    </div>\n                </div>\n            </div>\n\n        </div><!-- /content-area -->\n    </main>\n</div><!-- /app-container -->\n\n<!-- ============================================================\n     DEEP SEARCH MODAL\n     ============================================================ -->\n<div id=\"deepModal\" class=\"deepModal\">\n    <div class=\"spread\" style=\"margin-bottom:14px;\">\n        <h3>DEEP VISUAL INTEL</h3>\n        <button class=\"ctrl-btn stop\" onclick=\"closeDeepModal()\">[ X ] CLOSE</button>\n    </div>\n    <div class=\"row\" style=\"margin-bottom:14px; align-items:center;\">\n        <img id=\"deep-target-img\" src=\"\" style=\"width:84px; height:84px; object-fit:cover; border-radius:var(--radius); border:1px solid var(--cyan); box-shadow:0 0 22px rgba(0,240,255,0.4);\">\n        <div class=\"target-scan\">⚲ SCANNING TARGET ACROSS VISUAL SEARCH ENGINES...</div>\n    </div>\n    <div id=\"deep-status\" class=\"mono dim\" style=\"margin-bottom:14px;\">READY</div>\n    <div class=\"deep-grid\">\n        <div class=\"deep-col\"><h4>GOOGLE IMAGES</h4><div id=\"google-results\"></div></div>\n        <div class=\"deep-col\"><h4>BING VISUAL</h4><div id=\"bing-results\"></div></div>\n        <div class=\"deep-col\"><h4>YANDEX VISION</h4><div id=\"yandex-results\"></div></div>\n        <div class=\"deep-col\"><h4>TINEYE TRACKER</h4><div id=\"tineye-results\"></div></div>\n    </div>\n</div>\n\n<!-- ============================================================\n     SCRIPTS\n     ============================================================ -->\n<script src=\"https://unpkg.com/leaflet@1.9.4/dist/leaflet.js\"></script>\n<script>\n/* ---------- boot sequence ---------- */\n(function() {\n    const lines = [\n        '[OK] core kernel · online',\n        '[OK] neural mesh · linked',\n        '[OK] crypt engines · primed',\n        '[OK] phantom net · ready',\n        '[OK] sigint relay · open',\n        '[!!] uplink · god\\'s eye protocol engaged'\n    ];\n    const host = document.getElementById('boot-lines');\n    lines.forEach((line, i) => {\n        const span = document.createElement('span');\n        span.textContent = line;\n        if (line.includes('[!!]')) span.classList.add('err');\n        else span.classList.add('ok');\n        host.appendChild(span);\n        setTimeout(() => span.classList.add('show'), 180 + i * 200);\n    });\n    setTimeout(() => document.getElementById('boot').classList.add('done'), 1700);\n})();\n\n/* ---------- clock + session ---------- */\nfunction pad(n) { return String(n).padStart(2, '0'); }\nfunction tickClock() {\n    const d = new Date();\n    document.getElementById('clock').textContent =\n        `UTC ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`;\n}\nsetInterval(tickClock, 1000); tickClock();\ndocument.getElementById('session-id').textContent =\n    '#' + Math.random().toString(36).slice(2, 8).toUpperCase();\n\n/* ---------- particle canvas ---------- */\n(function() {\n    const c = document.getElementById('particle-canvas');\n    const ctx = c.getContext('2d');\n    let w, h, particles = [];\n    const COUNT = 80;\n\n    function resize() {\n        w = c.width = innerWidth;\n        h = c.height = innerHeight;\n    }\n    resize();\n    addEventListener('resize', resize);\n\n    for (let i = 0; i < COUNT; i++) {\n        particles.push({\n            x: Math.random() * w,\n            y: Math.random() * h,\n            vx: (Math.random() - 0.5) * 0.25,\n            vy: (Math.random() - 0.5) * 0.25,\n            r: Math.random() * 1.6 + 0.4,\n            hue: Math.random() < 0.5 ? '255, 0, 80' : '0, 240, 255'\n        });\n    }\n\n    function frame() {\n        ctx.clearRect(0, 0, w, h);\n        for (const p of particles) {\n            p.x += p.vx; p.y += p.vy;\n            if (p.x < 0 || p.x > w) p.vx *= -1;\n            if (p.y < 0 || p.y > h) p.vy *= -1;\n            ctx.beginPath();\n            ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);\n            ctx.fillStyle = `rgba(${p.hue}, 0.6)`;\n            ctx.fill();\n        }\n        // connect close particles\n        for (let i = 0; i < particles.length; i++) {\n            for (let j = i + 1; j < particles.length; j++) {\n                const a = particles[i], b = particles[j];\n                const dx = a.x - b.x, dy = a.y - b.y;\n                const d = Math.sqrt(dx * dx + dy * dy);\n                if (d < 120) {\n                    ctx.beginPath();\n                    ctx.moveTo(a.x, a.y);\n                    ctx.lineTo(b.x, b.y);\n                    ctx.strokeStyle = `rgba(255, 0, 80, ${0.08 * (1 - d / 120)})`;\n                    ctx.lineWidth = 0.5;\n                    ctx.stroke();\n                }\n            }\n        }\n        requestAnimationFrame(frame);\n    }\n    frame();\n})();\n\n/* ---------- tilt + cursor-glow on cards ---------- */\nfunction bindTilt(scope) {\n    (scope || document).querySelectorAll('.result-card').forEach(card => {\n        if (card.dataset.tiltBound) return;\n        card.dataset.tiltBound = '1';\n        card.addEventListener('mousemove', e => {\n            const r = card.getBoundingClientRect();\n            const x = e.clientX - r.left, y = e.clientY - r.top;\n            const rx = ((y / r.height) - 0.5) * -6;\n            const ry = ((x / r.width) - 0.5) * 6;\n            card.style.transform = `translateY(-4px) perspective(900px) rotateX(${rx}deg) rotateY(${ry}deg)`;\n            card.style.setProperty('--mx', x + 'px');\n            card.style.setProperty('--my', y + 'px');\n        });\n        card.addEventListener('mouseleave', () => {\n            card.style.transform = '';\n        });\n    });\n}\n\n/* ---------- scroll reveal ---------- */\nconst revealObserver = new IntersectionObserver(entries => {\n    for (const e of entries) {\n        if (e.isIntersecting) {\n            e.target.classList.add('visible');\n            revealObserver.unobserve(e.target);\n        }\n    }\n}, { threshold: 0.05 });\nfunction bindReveal(scope) {\n    (scope || document).querySelectorAll('.reveal').forEach(el => revealObserver.observe(el));\n}\nbindReveal();\n\n/* re-bind after each tab switch and after any innerHTML update on results */\nfunction refreshFX(scope) { bindTilt(scope); bindReveal(scope); }\n\n/* ---------- tab switching ---------- */\nfunction switchTab(tabId, title) {\n    document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));\n    document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));\n\n    const target = document.getElementById(tabId);\n    if (target) target.classList.add('active');\n\n    if (event && event.currentTarget) event.currentTarget.classList.add('active');\n    if (title) {\n        const tEl = document.getElementById('active-module-title');\n        tEl.textContent = '';\n        let i = 0;\n        const interval = setInterval(() => {\n            if (i >= title.length) { clearInterval(interval); return; }\n            tEl.textContent += title[i++];\n        }, 22);\n    }\n    refreshFX(target);\n}\n\n/* ============================================================\n   STATE + HELPERS\n   ============================================================ */\nlet jobId = null, pollInterval = null, currentUsername = \"\";\n\nfunction renderDict(obj, opts = {}) {\n    if (!obj || typeof obj !== 'object') return '';\n    return Object.entries(obj).map(([k, v]) => {\n        if (v === null || v === undefined || v === '') return '';\n        if (Array.isArray(v)) v = v.join(', ') || '—';\n        if (typeof v === 'object') v = JSON.stringify(v);\n        return `<div style=\"display:flex; gap:8px; padding:3px 0; font-size:0.82rem;\"><span class=\"dim mono\" style=\"min-width:120px;\">${k}:</span><span>${v}</span></div>`;\n    }).join('');\n}\n\nfunction gradeBadgeClass(g) {\n    if (g === 'A') return 'verified';\n    if (g === 'B') return 'low';\n    if (g === 'C') return 'medium';\n    if (g === 'D') return 'high';\n    return 'critical';\n}\n\nfunction fillScore(barEl, scoreEl, value, invert = false) {\n    barEl.style.width = value + '%';\n    scoreEl.textContent = value + '%';\n    let c = 'var(--green)';\n    if (invert) {\n        if (value > 70) c = 'var(--red)';\n        else if (value > 40) c = 'var(--amber)';\n    } else {\n        if (value < 30) c = 'var(--red)';\n        else if (value < 60) c = 'var(--amber)';\n    }\n    barEl.style.background = c;\n    barEl.style.color = c;\n}\n\n/* ============================================================\n   PROFILER\n   ============================================================ */\nasync function startScan() {\n    const username = document.getElementById('username').value.trim();\n    if (!username) return;\n    currentUsername = username;\n\n    document.getElementById('results').innerHTML = '';\n    document.getElementById('images-panel').classList.remove('hide');\n    document.getElementById('images-count').textContent = 'SEARCHING...';\n    document.getElementById('images-count').className = 'status-badge check';\n    document.getElementById('images-container').dataset.last = '';\n    document.getElementById('images-container').innerHTML = `\n        <div class=\"mono h-cyan\" style=\"padding:30px; text-align:center; width:100%; letter-spacing:2px;\">\n            <div style=\"font-size:0.9rem; margin-bottom:10px;\">⚲ ACQUIRING BIOMETRIC DATA</div>\n            <div class=\"dim\" style=\"font-size:0.74rem;\">querying ddg · bing · google images for \"${username}\"</div>\n        </div>`;\n    document.getElementById('status').textContent = 'INITIALIZING NEURAL LINK...';\n    document.getElementById('btn-scan').disabled = true;\n    document.getElementById('btn-stop').disabled = false;\n    document.getElementById('btn-download').disabled = true;\n    document.getElementById('username').disabled = true;\n\n    try {\n        const res = await fetch('/api/scan', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ username })\n        });\n        const data = await res.json();\n        jobId = data.job_id;\n        if (pollInterval) clearInterval(pollInterval);\n        pollInterval = setInterval(poll, 1500);\n    } catch (e) {\n        console.error(e);\n        alert('SYSTEM FAILURE: CONNECTION REFUSED');\n        resetState();\n    }\n}\n\nasync function stopScan() {\n    if (!jobId) return;\n    try {\n        await fetch(`/api/stop/${jobId}`, { method: 'POST' });\n        document.getElementById('status').textContent = 'ABORTING SEQUENCE...';\n    } catch (e) { console.error(e); }\n}\n\nfunction downloadReport() {\n    if (!jobId) return;\n    window.location.href = `/api/download/${jobId}`;\n}\n\nfunction executeDork() {\n    const u = (document.getElementById('username').value.trim() || currentUsername).trim();\n    if (!u) return alert('ENTER TARGET IDENTIFIER FIRST');\n    const type = document.getElementById('dork-select').value;\n    const map = {\n        linkedin: `site:linkedin.com \"${u}\"`, instagram: `site:instagram.com \"${u}\"`,\n        twitter: `site:twitter.com \"${u}\"`,  facebook: `site:facebook.com \"${u}\"`,\n        tiktok: `site:tiktok.com \"${u}\"`,    pinterest: `site:pinterest.com \"${u}\"`,\n        github: `site:github.com \"${u}\"`,    gitlab: `site:gitlab.com \"${u}\"`,\n        stackoverflow: `site:stackoverflow.com \"${u}\"`,\n        pastebin: `site:pastebin.com \"${u}\"`, reddit: `site:reddit.com \"${u}\"`,\n        pdf: `filetype:pdf \"${u}\"`, doc: `filetype:doc \"${u}\"`, txt: `filetype:txt \"${u}\"`,\n        intext: `intext:\"${u}\"`, intitle: `intitle:\"${u}\"`, inurl: `inurl:\"${u}\"`,\n        password: `\"${u}\" \"password\"`, email: `\"${u}\" email`, phone: `\"${u}\" phone`\n    };\n    const q = map[type];\n    if (q) window.open(`https://www.google.com/search?q=${encodeURIComponent(q)}`, '_blank');\n}\n\nfunction downloadImage(url) {\n    const a = document.createElement('a');\n    a.href = url; a.download = `target_${currentUsername}.jpg`; a.target = '_blank';\n    a.click();\n}\n\nasync function poll() {\n    if (!jobId) return;\n    try {\n        const res = await fetch(`/api/results/${jobId}`);\n        const data = await res.json();\n        renderImages(data.images || []);\n        renderResults(data.results);\n        const statusMap = {\n            running: 'SCANNING GLOBAL NETWORKS...',\n            validating: 'VERIFYING TARGET VULNERABILITIES [HEADLESS]...',\n            stopped: 'SEQUENCE ABORTED BY USER.',\n            completed: 'TARGET ACQUISITION COMPLETE.',\n            error: 'CRITICAL SYSTEM FAILURE.'\n        };\n        document.getElementById('status').textContent = statusMap[data.status] || data.status;\n        if (['completed', 'error', 'stopped'].includes(data.status)) {\n            clearInterval(pollInterval);\n            if (!data.images || !data.images.length) setImagesEmpty(data.image_diag);\n            resetState(true);\n        }\n    } catch (e) { console.error(e); }\n}\n\nfunction renderImages(images) {\n    const container = document.getElementById('images-container');\n    const countEl = document.getElementById('images-count');\n    if (!images || !images.length) return;\n    // Only re-render when the set changes\n    if (container.dataset.last === String(images.length)) return;\n    container.dataset.last = String(images.length);\n\n    countEl.textContent = `${images.length} IMAGE${images.length === 1 ? '' : 'S'} FOUND`;\n    countEl.className = 'status-badge verified';\n\n    container.innerHTML = images.map((src, i) => `\n        <div class=\"image-wrapper\" onclick=\"triggerDeepSearch('${src}', this)\" style=\"animation: tabEnter 480ms ${i * 80}ms both;\">\n            <img src=\"${src}\" class=\"captured-image\" onerror=\"this.parentElement.style.display='none'\">\n            <div style=\"position:absolute; top:8px; left:8px; padding:3px 8px; background:rgba(0,0,0,0.7); border:1px solid var(--cyan); border-radius:var(--radius-pill); font-family:var(--font-mono); font-size:0.62rem; letter-spacing:1px; color:var(--cyan);\">#${String(i + 1).padStart(2, '0')}</div>\n            <button class=\"btn-deep\">⚲ DEEP SEARCH</button>\n            <button class=\"dl-btn\" onclick=\"event.stopPropagation(); downloadImage('${src}')\">↓</button>\n        </div>\n    `).join('');\n}\n\nfunction setImagesEmpty(diag) {\n    const container = document.getElementById('images-container');\n    const countEl = document.getElementById('images-count');\n    if (container.children.length > 0 && !container.querySelector('.h-cyan')) return;\n    countEl.textContent = 'NO IMAGES';\n    countEl.className = 'status-badge clean';\n\n    const d = (diag || '').toLowerCase();\n    let hint = '';\n    if (d.includes('chromium-missing') || d.includes('browser-missing')) {\n        hint = `<div class=\"h-red mono\" style=\"font-size:0.74rem; margin-top:6px;\">⚠ Playwright Chromium is missing inside the container.<br>Fix: rebuild with <span class=\"h-cyan\">docker-compose build --no-cache</span> (the V5 Dockerfile re-runs <span class=\"h-cyan\">playwright install chromium</span>).</div>`;\n    } else if (d.includes('rate-limited')) {\n        hint = `<div class=\"h-amber mono\" style=\"font-size:0.74rem; margin-top:6px;\">⚠ DuckDuckGo rate-limited this IP. Wait a minute and retry, or run from a different IP.</div>`;\n    } else if (d.includes('timeout')) {\n        hint = `<div class=\"h-amber mono\" style=\"font-size:0.74rem; margin-top:6px;\">⚠ Network timeout reaching image engines.</div>`;\n    } else if (d) {\n        hint = `<div class=\"dim mono\" style=\"font-size:0.7rem; margin-top:6px;\">diag: ${diag}</div>`;\n    }\n    container.innerHTML = `\n        <div style=\"padding:20px; text-align:center; width:100%;\">\n            <div class=\"dim mono\" style=\"font-size:0.82rem;\">[ NO PUBLIC IMAGES FOUND FOR THIS TARGET ]</div>\n            ${hint}\n        </div>`;\n}\n\nfunction renderResults(results) {\n    const container = document.getElementById('results');\n    const rank = s => s === 'Verified' ? 0 : s === 'Checking...' ? 1 : s === 'Pending' ? 2 : 3;\n    const sorted = [...results].sort((a, b) => rank(a.validation) - rank(b.validation));\n    container.innerHTML = sorted.map(r => `\n        <div class=\"result-card\">\n            <div class=\"spread\" style=\"margin-bottom:8px;\">\n                <h3 class=\"card-title\">${r.site}</h3>\n                ${renderBadge(r)}\n            </div>\n            <a href=\"${r.url}\" target=\"_blank\" class=\"card-url\">${r.url}</a>\n            ${r.page_title ? `<div class=\"dim mono\" style=\"font-size:0.74rem;\">TITLE: ${r.page_title}</div>` : ''}\n            ${r.reason ? `<div class=\"h-red mono\" style=\"font-size:0.74rem;\">ERR: ${r.reason}</div>` : ''}\n        </div>\n    `).join('');\n    refreshFX(container);\n}\n\nfunction renderBadge(res) {\n    if (res.validation === 'Pending') return '<span class=\"status-badge\">PENDING</span>';\n    if (res.validation === 'Checking...') return '<span class=\"status-badge check\">CHECKING</span>';\n    if (res.validation === 'Verified') return '<span class=\"status-badge verified\">VERIFIED</span>';\n    if (res.validation === 'False Positive') return '<span class=\"status-badge false\">FALSE+</span>';\n    return '';\n}\n\nfunction resetState(finished = false) {\n    document.getElementById('btn-scan').disabled = false;\n    document.getElementById('btn-stop').disabled = true;\n    document.getElementById('btn-download').disabled = !finished;\n    document.getElementById('username').disabled = false;\n}\n\n/* ============================================================\n   DEEP SEARCH MODAL\n   ============================================================ */\nasync function triggerDeepSearch(url, wrapper) {\n    document.getElementById('deep-target-img').src = url;\n    const modal = document.getElementById('deepModal');\n    modal.classList.add('active');\n    document.getElementById('deep-status').textContent = 'DEPLOYING MULTI-VECTOR VISUAL SEARCH...';\n    ['google-results', 'bing-results', 'yandex-results', 'tineye-results'].forEach(id => {\n        document.getElementById(id).innerHTML = '<div class=\"h-green mono\" style=\"animation: pulse 1.2s ease-in-out infinite;\">[ ESTABLISHING UPLINK... ]</div>';\n    });\n\n    try {\n        const res = await fetch('/api/deep-search', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ image_url: url })\n        });\n        const data = await res.json();\n        document.getElementById('deep-status').textContent = 'TARGET VISUAL MATCHES ACQUIRED.';\n        renderDeepResults('google-results', data.google);\n        renderDeepResults('bing-results', data.bing);\n        renderDeepResults('yandex-results', data.yandex);\n        renderDeepResults('tineye-results', data.tineye);\n    } catch (e) {\n        console.error(e);\n        document.getElementById('deep-status').textContent = 'VISUAL SEARCH FAILURE.';\n    }\n}\n\nfunction renderDeepResults(divId, images) {\n    const c = document.getElementById(divId);\n    if (!images || images.length === 0) {\n        c.innerHTML = '<div class=\"dim\">NO MATCHES FOUND</div>';\n        return;\n    }\n    c.innerHTML = images.map(src => `<img src=\"${src}\" onclick=\"window.open('${src}')\">`).join('');\n}\n\nfunction closeDeepModal() {\n    document.getElementById('deepModal').classList.remove('active');\n}\n\n/* ============================================================\n   PHANTOM ID\n   ============================================================ */\nasync function scanPhantom() {\n    const username = document.getElementById('phantom-input').value.trim();\n    if (!username) return;\n    document.getElementById('phantom-status').textContent = 'HUNTING USERNAME ACROSS 200+ PLATFORMS...';\n    document.getElementById('phantom-grid').innerHTML = '<div class=\"mono h-cyan\" style=\"text-align:center; padding:40px; width:100%;\">SCANNING NETWORKS...</div>';\n    document.getElementById('phantom-meta').classList.add('hide');\n    try {\n        const res = await fetch('/api/phantom', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ username })\n        });\n        const data = await res.json();\n\n        const bar = document.getElementById('phantom-score-bar');\n        const text = document.getElementById('phantom-score-text');\n        fillScore(bar, text, data.risk_score || 0, true);\n\n        document.getElementById('phantom-avatar').src = data.gravatar || '';\n        document.getElementById('phantom-meta').classList.remove('hide');\n        document.getElementById('phantom-cats').innerHTML =\n            Object.entries(data.categories || {}).map(([c, n]) => `<span class=\"tag\">${c.toUpperCase()}: ${n}</span>`).join('');\n\n        const grid = document.getElementById('phantom-grid');\n        if (!data.profiles || !data.profiles.length) {\n            grid.innerHTML = '<div class=\"dim mono\" style=\"text-align:center; padding:30px;\">NO PROFILES FOUND.</div>';\n        } else {\n            grid.innerHTML = data.profiles.map(p => `\n                <div class=\"result-card\" style=\"cursor:pointer;\" onclick=\"window.open('${p.url}')\">\n                    <h3 class=\"card-title\">${p.platform}</h3>\n                    <div class=\"dim mono\" style=\"font-size:0.74rem;\">STATUS ${p.status} · ${p.cat || 'general'}</div>\n                </div>\n            `).join('');\n        }\n        document.getElementById('phantom-status').textContent =\n            `FOUND PRESENCE ON ${data.found} / ${data.total_checked} PLATFORMS.`;\n        refreshFX(grid);\n    } catch (e) {\n        console.error(e);\n        document.getElementById('phantom-status').textContent = 'PHANTOM IDENTIFICATION FAILED.';\n    }\n}\n\n/* ============================================================\n   BREACH VAULT\n   ============================================================ */\nasync function scanBreach() {\n    const query = document.getElementById('breach-input').value.trim();\n    const type = document.getElementById('breach-type').value;\n    if (!query) return;\n    document.getElementById('breach-status').textContent = 'QUERYING DARK VAULTS...';\n    document.getElementById('breach-results').innerHTML = '<div class=\"mono h-cyan\" style=\"text-align:center; padding:40px;\">ANALYZING LEAKS...</div>';\n    document.getElementById('breach-summary').innerHTML = '';\n    document.getElementById('paste-dump-results').innerHTML = '';\n\n    try {\n        const res = await fetch('/api/breach', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ query, type })\n        });\n        const data = await res.json();\n\n        if (data.type === 'password') {\n            if (data.pwned) {\n                document.getElementById('breach-results').innerHTML = `\n                <div class=\"result-card\" style=\"border-color:var(--red); text-align:center;\">\n                    <h3 class=\"card-title h-red\">PASSWORD COMPROMISED</h3>\n                    <div class=\"mono\" style=\"font-size:1.2rem;\">SEEN ${data.count} TIMES</div>\n                    <div class=\"dim mono\" style=\"font-size:0.78rem; margin-top:8px;\">DO NOT USE THIS PASSWORD</div>\n                </div>`;\n                document.getElementById('breach-status').textContent = 'WARNING: HIGH SEVERITY.';\n            } else {\n                document.getElementById('breach-results').innerHTML = `\n                <div class=\"result-card\" style=\"border-color:var(--green); text-align:center;\">\n                    <h3 class=\"card-title h-green\">PASSWORD SAFE</h3>\n                    <div class=\"mono\">NO MATCHES IN KNOWN DATABASES.</div>\n                </div>`;\n                document.getElementById('breach-status').textContent = 'NO KNOWN EXPOSURE.';\n            }\n            refreshFX(document.getElementById('breach-results'));\n            return;\n        }\n\n        document.getElementById('breach-summary').innerHTML = `\n            <div class=\"result-card\" style=\"display:flex; gap:24px; justify-content:center; text-align:center;\">\n                <div><div style=\"font-size:2rem; font-family:var(--font-display);\" class=\"h-red\">${data.breach_count}</div><div class=\"dim mono\" style=\"font-size:0.7rem;\">DATABASES</div></div>\n                <div><div style=\"font-size:2rem; font-family:var(--font-display);\" class=\"h-amber\">${data.paste_count}</div><div class=\"dim mono\" style=\"font-size:0.7rem;\">PASTES</div></div>\n                <div><div style=\"font-size:2rem; font-family:var(--font-display);\" class=\"h-cyan\">${(data.total_records_exposed||0).toLocaleString()}</div><div class=\"dim mono\" style=\"font-size:0.7rem;\">RECORDS</div></div>\n            </div>`;\n\n        let html = \"\";\n        (data.breaches || []).forEach(b => {\n            if (b._note) { html += `<div class=\"dim mono\" style=\"text-align:center;\">${b._note}</div>`; return; }\n            const sev = (b.severity || 'LOW').toLowerCase();\n            html += `\n            <div class=\"result-card\">\n                <div class=\"spread\" style=\"margin-bottom:8px;\">\n                    <h3 class=\"card-title\">${b.name}</h3>\n                    <span class=\"status-badge ${sev}\">${b.severity}</span>\n                </div>\n                <div class=\"row\" style=\"gap:14px;\">\n                    ${b.logo ? `<img src=\"${b.logo}\" style=\"width:48px; height:48px; object-fit:contain; border-radius:6px;\" onerror=\"this.style.display='none'\">` : ''}\n                    <div style=\"flex:1;\">\n                        <div class=\"dim mono\" style=\"font-size:0.74rem; margin-bottom:6px;\">${b.date} · ${b.domain || 'unknown'}</div>\n                        <div style=\"font-size:0.85rem; margin-bottom:8px;\">${b.description}</div>\n                        <div class=\"row\" style=\"gap:4px;\">${(b.data_classes || []).map(d => `<span class=\"tag hot\">${d}</span>`).join('')}</div>\n                    </div>\n                </div>\n            </div>`;\n        });\n        if (!data.breaches || !data.breaches.length) {\n            html = '<div class=\"result-card\" style=\"border-color:var(--green); text-align:center;\"><h3 class=\"card-title h-green\">CLEAN REPUTATION</h3><div>NO BREACHES DETECTED.</div></div>';\n        }\n        document.getElementById('breach-results').innerHTML = html;\n\n        if (data.paste_dumps && data.paste_dumps.length) {\n            const pd = data.paste_dumps.map(p => `\n                <div class=\"result-card\" style=\"display:flex; justify-content:space-between; align-items:center;\">\n                    <div>\n                        <strong class=\"h-amber\">Pastebin ID: ${p.id}</strong>\n                        <div class=\"dim mono\" style=\"font-size:0.74rem;\">Indexed: ${new Date(p.date * 1000).toISOString().split('T')[0]}</div>\n                    </div>\n                    <button class=\"dork-btn\" onclick=\"window.open('${p.url}')\">VIEW CACHE ›</button>\n                </div>\n            `).join('');\n            document.getElementById('paste-dump-results').innerHTML = pd;\n        }\n        document.getElementById('breach-status').textContent = 'AUDIT COMPLETE.';\n        refreshFX(document.getElementById('breach-results'));\n    } catch (e) {\n        console.error(e);\n        document.getElementById('breach-status').textContent = 'VAULT CONNECTION TERMINATED.';\n    }\n}\n\n/* ============================================================\n   FOOTPRINT\n   ============================================================ */\nasync function scanFootprint() {\n    const query = document.getElementById('footprint-input').value.trim();\n    const type = document.getElementById('footprint-type').value;\n    if (!query) return;\n    document.getElementById('footprint-status').textContent = 'SCANNING DIGITAL TRACE...';\n    document.getElementById('footprint-results').innerHTML = '<div class=\"mono h-cyan\" style=\"text-align:center; padding:30px;\">ACCESSING DATABASES...</div>';\n    try {\n        const res = await fetch('/api/footprint', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ query, type })\n        });\n        const data = await res.json();\n        if (data.error) {\n            document.getElementById('footprint-results').innerHTML = `<div class=\"result-card\" style=\"border-color:var(--red);\">${data.error}</div>`;\n            document.getElementById('footprint-status').textContent = 'ERROR.';\n            return;\n        }\n        if (type === 'phone') {\n            const p = data;\n            document.getElementById('footprint-results').innerHTML = `\n                <div class=\"result-card\">\n                    <h3 class=\"card-title h-green\">PHONE INTELLIGENCE</h3>\n                    ${renderDict({\n                        'VALID NUMBER': p.valid,\n                        'FORMAT': p.number,\n                        'COUNTRY': p.country,\n                        'CARRIER': p.carrier,\n                        'LINE TYPE': p.line_type,\n                        'TIMEZONES': (p.timezones || []).join(', ')\n                    })}\n                </div>`;\n            document.getElementById('footprint-status').textContent = 'PHONE TRACE COMPLETE.';\n        } else {\n            let html = '';\n            if (data.found_on && data.found_on.length) {\n                html += `<div class=\"result-card\"><h3 class=\"card-title h-green\">DIGITAL FOOTPRINT DETECTED</h3>\n                    <div class=\"results\" style=\"grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap:8px; margin-top:10px;\">\n                    ${data.found_on.map(s => `<div class=\"tag ok\" style=\"text-align:center; padding:8px;\">${s}</div>`).join('')}\n                    </div></div>`;\n            } else {\n                html += `<div class=\"result-card\" style=\"text-align:center;\"><h3 class=\"card-title dim\">NO PUBLIC TRACE</h3><div class=\"dim\">TARGET EMAIL APPEARS CLEAN.</div></div>`;\n            }\n            html += `<div class=\"result-card\" style=\"margin-top:12px; border-color:${data.valid_mx ? 'var(--green)' : 'var(--red)'};\">\n                <h3 class=\"card-title\">MX RECORDS</h3>\n                <div class=\"mono\" style=\"font-size:1.05rem;\">${data.valid_mx ? 'VALID MAIL SERVER' : 'INVALID / UNREACHABLE'}</div>\n                <div class=\"dim mono\" style=\"font-size:0.78rem;\">${(data.mx_records || []).length} SERVERS</div>\n            </div>`;\n            document.getElementById('footprint-results').innerHTML = html;\n            document.getElementById('footprint-status').textContent =\n                (data.found_on || []).length ? `EMAIL FOUND ON ${data.found_on.length} PLATFORMS.` : 'NO PUBLIC TRACE.';\n        }\n        refreshFX(document.getElementById('footprint-results'));\n    } catch (e) {\n        document.getElementById('footprint-status').textContent = 'ERROR EXECUTING TRACE.';\n    }\n}\n\n/* ============================================================\n   NETWORK MAPPER\n   ============================================================ */\nasync function scanNetwork() {\n    const domain = document.getElementById('network-input').value.trim();\n    if (!domain) return;\n    document.getElementById('network-status').textContent = 'MAPPING TOPOLOGY...';\n    document.getElementById('network-graph').innerHTML = '<div class=\"mono h-cyan\" style=\"text-align:center; padding:100px;\">ESTABLISHING CONNECTIONS...</div>';\n    document.getElementById('network-ports').innerHTML = '';\n    try {\n        const res = await fetch('/api/network/scan', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ domain })\n        });\n        const data = await res.json();\n        if (data.error) { document.getElementById('network-status').textContent = 'ERROR: ' + data.error; return; }\n        if (data.map_html) {\n            const blob = new Blob([data.map_html], { type: 'text/html' });\n            const url = URL.createObjectURL(blob);\n            document.getElementById('network-graph').innerHTML = `<iframe src=\"${url}\" style=\"width:100%; height:100%; border:none;\"></iframe>`;\n        }\n        let intel = '<div class=\"results\" style=\"grid-template-columns: 1fr 1fr 1fr; gap:14px;\">';\n        intel += `<div class=\"result-card\"><h3 class=\"card-title\">OPEN PORTS</h3>${\n            (data.ports || []).length\n                ? data.ports.map(p => `<div class=\"row\" style=\"font-family:var(--font-mono); font-size:0.84rem;\"><span class=\"h-red mono\">${p.port}</span><span>${p.service}</span></div>`).join('')\n                : '<div class=\"dim\">NO OPEN PORTS DETECTED</div>'\n        }</div>`;\n        intel += `<div class=\"result-card\"><h3 class=\"card-title\">GEO / WHOIS</h3>${\n            renderDict({\n                LOC: data.geoip && data.geoip.country ? `${data.geoip.country} (${data.geoip.countryCode})` : null,\n                ISP: data.geoip && data.geoip.isp,\n                ORG: data.geoip && data.geoip.org,\n                REG: data.whois && data.whois.registrar,\n                DATE: data.whois && data.whois.creation_date\n            })\n        }</div>`;\n        intel += `<div class=\"result-card\"><h3 class=\"card-title\">DNS RECORDS</h3>${\n            renderDict({\n                MX: (data.dns && data.dns.MX || []).join(', ') || null,\n                NS: (data.dns && data.dns.NS || []).join(', ') || null,\n                A:  (data.dns && data.dns.A  || []).join(', ') || null,\n                TXT: (data.dns && data.dns.TXT && data.dns.TXT.length) ? `${data.dns.TXT.length} records` : null\n            })\n        }</div></div>`;\n        document.getElementById('network-ports').innerHTML = intel;\n        document.getElementById('network-status').textContent = 'SCAN COMPLETE.';\n        refreshFX(document.getElementById('network-ports'));\n    } catch (e) {\n        document.getElementById('network-status').textContent = 'NETWORK ERROR.';\n    }\n}\n\n/* ============================================================\n   DARK WEB\n   ============================================================ */\nasync function scanDarkWeb() {\n    const query = document.getElementById('dark-input').value.trim();\n    if (!query) return;\n    document.getElementById('dark-status').textContent = 'ROUTING THROUGH TOR GATEWAYS...';\n    document.getElementById('dark-results').innerHTML = '<div class=\"mono h-amber\" style=\"text-align:center;\">DECYPHERING...</div>';\n    try {\n        const res = await fetch('/api/dark/search', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ query })\n        });\n        const data = await res.json();\n        if (data.error) { document.getElementById('dark-results').innerHTML = `<div class=\"h-red\">ERROR: ${data.error}</div>`; return; }\n        if (!data.results.length) { document.getElementById('dark-results').innerHTML = '<div class=\"dim mono\" style=\"text-align:center;\">NO HIDDEN SERVICES FOUND.</div>'; return; }\n        document.getElementById('dark-results').innerHTML = data.results.map(item => {\n            const isRansom = (item.title || '').includes('Ransomware');\n            return `\n            <div class=\"result-card\">\n                <div class=\"spread\" style=\"margin-bottom:8px;\">\n                    <h3 class=\"card-title h-red\">${item.title}</h3>\n                    <span class=\"status-badge ${isRansom ? 'critical' : 'high'}\">${isRansom ? 'LEAK' : 'ONION'}</span>\n                </div>\n                <div class=\"dim mono\" style=\"font-size:0.74rem;\">${item.date || ''}</div>\n                <div style=\"font-size:0.86rem; margin:8px 0;\">${item.snippet || ''}</div>\n                <a href=\"${item.link}\" target=\"_blank\" class=\"card-url\">${item.link}</a>\n            </div>`;\n        }).join('');\n        document.getElementById('dark-status').textContent = `FOUND ${data.count} ONION SERVICES.`;\n        refreshFX(document.getElementById('dark-results'));\n    } catch (e) { document.getElementById('dark-status').textContent = 'CONNECTION FAILURE.'; }\n}\n\n/* ============================================================\n   CRYPTO\n   ============================================================ */\nasync function scanCrypto() {\n    const address = document.getElementById('crypto-input').value.trim();\n    const coin = document.getElementById('crypto-type').value;\n    if (!address) return;\n    document.getElementById('crypto-status').textContent = 'SYNCING WITH DISTRIBUTED LEDGER...';\n    document.getElementById('crypto-results').innerHTML = '<div class=\"mono h-cyan\" style=\"text-align:center; padding:40px;\">FETCHING BLOCKS...</div>';\n    try {\n        const res = await fetch('/api/crypto/analyze', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ address, coin })\n        });\n        const data = await res.json();\n        if (data.error) { document.getElementById('crypto-status').textContent = 'ERROR: ' + data.error; document.getElementById('crypto-results').innerHTML = ''; return; }\n        document.getElementById('crypto-results').innerHTML = `\n        <div class=\"result-card\" style=\"max-width:760px; margin:0 auto;\">\n            <div class=\"spread\" style=\"margin-bottom:14px;\">\n                <h3 class=\"card-title h-amber\">${data.coin.toUpperCase()} WALLET</h3>\n                <span class=\"status-badge verified\">ACTIVE</span>\n            </div>\n            <div class=\"results\" style=\"grid-template-columns: 1fr 1fr; gap:14px;\">\n                <div><div class=\"dim mono\" style=\"font-size:0.74rem;\">BALANCE</div><div style=\"font-size:2rem; font-family:var(--font-display);\">${data.balance}</div></div>\n                <div><div class=\"dim mono\" style=\"font-size:0.74rem;\">TOTAL RECEIVED</div><div style=\"font-size:2rem; font-family:var(--font-display);\">${data.total_received}</div></div>\n                <div><div class=\"dim mono\" style=\"font-size:0.74rem;\">TRANSACTIONS</div><div style=\"font-size:1.4rem; font-family:var(--font-display);\">${data.tx_count}</div></div>\n                <div><div class=\"dim mono\" style=\"font-size:0.74rem;\">LAST ACTIVITY</div><div style=\"font-size:1.4rem; font-family:var(--font-display);\">${data.last_seen}</div></div>\n            </div>\n            <div class=\"dim mono\" style=\"margin-top:14px; word-break:break-all; font-size:0.78rem;\">ADDR: ${data.address}</div>\n        </div>`;\n        document.getElementById('crypto-status').textContent = `ANALYSIS COMPLETE: ${data.coin.toUpperCase()}`;\n        refreshFX(document.getElementById('crypto-results'));\n    } catch (e) { document.getElementById('crypto-status').textContent = 'LEDGER SYNC FAILED.'; }\n}\n\n/* ============================================================\n   SSL SENTINEL\n   ============================================================ */\nasync function scanSSL() {\n    const domain = document.getElementById('ssl-input').value.trim();\n    if (!domain) return;\n    document.getElementById('ssl-status').textContent = 'INITIATING SSL HANDSHAKE...';\n    document.getElementById('ssl-results').innerHTML = '<div class=\"mono h-cyan\" style=\"text-align:center; padding:40px;\">DECODING CERTIFICATE CHAIN...</div>';\n    try {\n        const res = await fetch('/api/ssl/scan', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ domain })\n        });\n        const data = await res.json();\n        if (data.error) { document.getElementById('ssl-status').textContent = 'SSL FAIL: ' + data.error; document.getElementById('ssl-results').innerHTML = ''; return; }\n        document.getElementById('ssl-results').innerHTML = `\n        <div class=\"result-card\">\n            <div class=\"spread\" style=\"margin-bottom:12px;\">\n                <h3 class=\"card-title h-cyan\">${data.domain}</h3>\n                <span class=\"status-badge ${data.expired ? 'critical' : 'verified'}\">${data.expired ? 'EXPIRED' : 'VALID'}</span>\n            </div>\n            <div class=\"results\" style=\"grid-template-columns: 1fr 1fr; gap:14px;\">\n                <div>\n                    <h4 class=\"h-cyan mono\" style=\"margin:0 0 8px;\">ISSUED BY</h4>\n                    ${renderDict(data.issuer)}\n                    <div class=\"dim mono\" style=\"font-size:0.74rem; margin-top:10px;\">VALIDITY</div>\n                    <div class=\"mono\" style=\"font-size:0.84rem;\">${data.not_before} → ${data.not_after}</div>\n                </div>\n                <div>\n                    <h4 class=\"h-cyan mono\" style=\"margin:0 0 8px;\">ISSUED TO</h4>\n                    ${renderDict(data.subject)}\n                </div>\n            </div>\n            <div style=\"margin-top:14px;\">\n                <h4 class=\"h-cyan mono\" style=\"margin:0 0 8px;\">SUBDOMAINS (SANs) [${(data.sans || []).length}]</h4>\n                <div>${(data.sans || []).map(s => `<span class=\"tag\">${s}</span>`).join('')}</div>\n            </div>\n        </div>`;\n        document.getElementById('ssl-status').textContent = 'CERTIFICATE VERIFIED.';\n        refreshFX(document.getElementById('ssl-results'));\n    } catch (e) { document.getElementById('ssl-status').textContent = 'HANDSHAKE FAILED.'; }\n}\n\n/* ============================================================\n   EXIF\n   ============================================================ */\nasync function analyzeExif() {\n    const url = document.getElementById('exif-input').value.trim();\n    if (!url) return;\n    document.getElementById('exif-status').textContent = 'DOWNLOADING RAW BYTES...';\n    document.getElementById('exif-results').innerHTML = '<div class=\"mono h-cyan\" style=\"text-align:center; padding:40px;\">EXTRACTING METADATA LAYERS...</div>';\n    try {\n        const res = await fetch('/api/tools/exif', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ url })\n        });\n        renderExif(await res.json());\n    } catch (e) { document.getElementById('exif-status').textContent = 'ANALYSIS ERROR.'; }\n}\n\nasync function analyzeExifFile() {\n    const fileInput = document.getElementById('exif-file');\n    if (!fileInput.files.length) return;\n    document.getElementById('exif-status').textContent = 'UPLOADING BITSTREAM...';\n    document.getElementById('exif-results').innerHTML = '<div class=\"mono h-cyan\" style=\"text-align:center; padding:40px;\">EXTRACTING...</div>';\n    const fd = new FormData(); fd.append('file', fileInput.files[0]);\n    try {\n        const res = await fetch('/api/tools/exif/upload', { method: 'POST', body: fd });\n        renderExif(await res.json());\n    } catch (e) { document.getElementById('exif-status').textContent = 'UPLOAD FAILED.'; }\n}\n\nfunction renderExif(data) {\n    if (data.error) { document.getElementById('exif-status').textContent = 'EXTRACT FAIL: ' + data.error; document.getElementById('exif-results').innerHTML = ''; return; }\n    document.getElementById('exif-status').textContent = 'METADATA REVEALED.';\n    const gpsBlock = data.gps && Object.keys(data.gps).length\n        ? `<h4 class=\"h-red mono\" style=\"margin-top:14px;\">GEOLOCATION DATA</h4>${renderDict(data.gps)}`\n        : `<div class=\"dim mono\" style=\"margin-top:14px;\">[NO GPS DATA EMBEDDED]</div>`;\n    document.getElementById('exif-results').innerHTML = `\n    <div class=\"result-card\">\n        <div class=\"spread\" style=\"margin-bottom:10px;\">\n            <h3 class=\"card-title\">IMAGE METADATA</h3>\n            <span class=\"status-badge info\">EXIF</span>\n        </div>\n        <h4 class=\"h-cyan mono\" style=\"margin:0 0 8px;\">BASIC INFO</h4>\n        ${renderDict(data.basic)}\n        ${gpsBlock}\n    </div>`;\n    refreshFX(document.getElementById('exif-results'));\n}\n\n/* ============================================================\n   DORKS\n   ============================================================ */\nasync function generateDorks() {\n    const target = document.getElementById('dork-target').value.trim();\n    const domain = document.getElementById('dork-domain').value.trim();\n    if (!target) return;\n    try {\n        const res = await fetch('/api/tools/dork', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ target, domain })\n        });\n        const data = await res.json();\n        const sections = [\n            { key: 'google', title: 'GOOGLE (EXTREME)', color: 'var(--cyan)', baseUrl: 'https://www.google.com/search?q=' },\n            { key: 'shodan', title: 'SHODAN (INFRA)', color: 'var(--amber)', baseUrl: 'https://www.shodan.io/search?query=' },\n            { key: 'github', title: 'GITHUB (LEAKS)', color: 'var(--violet)', baseUrl: 'https://github.com/search?type=Code&q=' }\n        ];\n        let html = '';\n        for (const s of sections) {\n            if (!data[s.key] || !data[s.key].length) continue;\n            html += `<h3 class=\"panel-title\" style=\"margin-top:14px; color:${s.color};\">${s.title}</h3>`;\n            html += data[s.key].map(d => `\n            <div class=\"result-card\">\n                <h3 class=\"card-title\">${d.title}</h3>\n                <div class=\"mono\" style=\"background:rgba(0,0,0,0.5); padding:10px; border-radius:6px; margin:8px 0; word-break:break-all; font-size:0.78rem; color:var(--cyan);\">${d.query}</div>\n                <button class=\"dork-btn\" onclick=\"window.open('${s.baseUrl}${encodeURIComponent(d.query)}')\">OPEN ${s.title.split(' ')[0]} ›</button>\n            </div>`).join('');\n        }\n        document.getElementById('dork-results').innerHTML = html;\n        document.getElementById('dork-status').textContent = 'ATTACK VECTORS GENERATED.';\n        refreshFX(document.getElementById('dork-results'));\n    } catch (e) { console.error(e); }\n}\n\n/* ============================================================\n   GEOINT\n   ============================================================ */\nasync function runGeoint() {\n    const lat = document.getElementById('geo-lat').value.trim();\n    const lon = document.getElementById('geo-lon').value.trim();\n    if (!lat || !lon) return;\n    document.getElementById('geo-status').textContent = 'TRIANGULATING POSITION...';\n    document.getElementById('geo-results').innerHTML = '<div class=\"mono h-green\" style=\"text-align:center; padding:40px;\">ACQUIRING SATELLITE LOCK...</div>';\n    try {\n        const res = await fetch('/api/tools/geoint', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ lat, lon })\n        });\n        const data = await res.json();\n        if (data.error) { document.getElementById('geo-status').textContent = data.error; document.getElementById('geo-results').innerHTML = ''; return; }\n        const links = Object.entries(data.links || {}).map(([k, url]) => `\n            <div class=\"result-card\">\n                <h3 class=\"card-title h-green\">${k}</h3>\n                <button class=\"dork-btn\" style=\"background:rgba(0,255,140,0.15); color:var(--green); border-color:var(--green);\" onclick=\"window.open('${url}')\">LAUNCH VIEW ›</button>\n            </div>`).join('');\n        document.getElementById('geo-results').innerHTML = `<div class=\"results\" style=\"grid-template-columns:1fr 1fr; gap:12px;\">${links}</div>`;\n        document.getElementById('geo-status').textContent = `TARGET ACQUIRED: ${data.coords}`;\n        refreshFX(document.getElementById('geo-results'));\n    } catch (e) { document.getElementById('geo-status').textContent = 'SATELLITE LINK FAILED.'; }\n}\n\n/* ============================================================\n   SKY RADAR\n   ============================================================ */\nlet flightMap = null;\nasync function scanSky() {\n    const lat = document.getElementById('sky-lat').value.trim();\n    const lon = document.getElementById('sky-lon').value.trim();\n    const rad = document.getElementById('sky-rad').value.trim();\n    if (!lat || !lon) return;\n    document.getElementById('sky-status').textContent = 'SCANNING AIRSPACE...';\n    document.getElementById('sky-results').innerHTML = '<div class=\"mono h-cyan\" style=\"text-align:center;\">READING TRANSPONDERS...</div>';\n    if (!flightMap) {\n        flightMap = L.map('sky-map').setView([lat, lon], 7);\n        L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png', {\n            attribution: '&copy; OSM &copy; CartoDB', subdomains: 'abcd', maxZoom: 19\n        }).addTo(flightMap);\n    } else {\n        flightMap.setView([lat, lon], 7);\n        flightMap.eachLayer(layer => { if (layer instanceof L.Marker) flightMap.removeLayer(layer); });\n    }\n    L.marker([lat, lon]).addTo(flightMap).bindPopup('TARGET LOCATION').openPopup();\n\n    try {\n        const res = await fetch('/api/tools/flight', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ lat: parseFloat(lat), lon: parseFloat(lon), radius: parseFloat(rad) })\n        });\n        const data = await res.json();\n        if (data.error || data.message) {\n            document.getElementById('sky-status').textContent = 'SECTOR CLEAR or ERROR.';\n            document.getElementById('sky-results').innerHTML = `<div class=\"dim mono\" style=\"text-align:center;\">${data.error || data.message}</div>`;\n            return;\n        }\n        let listHtml = '';\n        data.flights.forEach(f => {\n            if (f.lat && f.lon) {\n                const ico = `<div style=\"color:var(--cyan); font-size:20px; transform: rotate(${f.true_track || 0}deg); text-shadow:0 0 5px var(--cyan);\">✈</div>`;\n                const divIcon = L.divIcon({ html: ico, className: 'plane-icon', iconSize: [24, 24], popupAnchor: [0, -10] });\n                L.marker([f.lat, f.lon], { icon: divIcon }).addTo(flightMap)\n                    .bindPopup(`<strong style=\"color:var(--cyan)\">${f.callsign || 'UNKNOWN'}</strong><br>${f.country}<br>Alt: ${f.alt}m | Vel: ${f.velocity}m/s`);\n            }\n            listHtml += `\n            <div class=\"result-card\" style=\"cursor:pointer; margin-bottom:8px;\" onclick=\"focusMap(${f.lat}, ${f.lon}, '${f.callsign || ''}')\">\n                <div class=\"spread\" style=\"margin-bottom:6px;\">\n                    <h3 class=\"card-title h-cyan\">${f.callsign || 'UNK'}</h3>\n                    <span class=\"tag\">${(f.country || '').substring(0, 3).toUpperCase()}</span>\n                </div>\n                <div class=\"row mono\" style=\"font-size:0.78rem; justify-content:space-between;\">\n                    <span class=\"dim\">ALT: ${f.alt}m</span>\n                    <span class=\"dim\">VEL: ${f.velocity}m/s</span>\n                </div>\n            </div>`;\n        });\n        document.getElementById('sky-results').innerHTML = listHtml || '<div class=\"dim mono\" style=\"text-align:center;\">NO TARGETS IN RANGE</div>';\n        document.getElementById('sky-status').textContent = `TRACKING ${data.count} AIRCRAFT.`;\n    } catch (e) { document.getElementById('sky-status').textContent = 'RADAR MALFUNCTION.'; }\n}\n\nfunction focusMap(lat, lon, callsign) {\n    if (flightMap) flightMap.setView([lat, lon], 11);\n}\n\nfunction updateSkyRegion() {\n    const r = {\n        uk: { lat: 51.5074, lon: -0.1278, rad: 300 },\n        usa_east: { lat: 40.7128, lon: -74.0060, rad: 500 },\n        usa_west: { lat: 34.0522, lon: -118.2437, rad: 500 },\n        france: { lat: 48.8566, lon: 2.3522, rad: 300 },\n        germany: { lat: 52.5200, lon: 13.4050, rad: 300 },\n        russia: { lat: 55.7558, lon: 37.6173, rad: 400 },\n        china: { lat: 31.2304, lon: 121.4737, rad: 500 },\n        japan: { lat: 35.6762, lon: 139.6503, rad: 300 },\n        dubai: { lat: 25.2048, lon: 55.2708, rad: 200 }\n    };\n    const sel = document.getElementById('sky-country').value;\n    if (r[sel]) {\n        document.getElementById('sky-lat').value = r[sel].lat;\n        document.getElementById('sky-lon').value = r[sel].lon;\n        document.getElementById('sky-rad').value = r[sel].rad;\n    }\n}\n\n/* ============================================================\n   SIGINT SWEEP\n   ============================================================ */\nasync function scanSigint() {\n    const query = document.getElementById('sigint-input').value.trim();\n    if (!query) return;\n    document.getElementById('sigint-status').textContent = 'INTERCEPTING TRANSMISSIONS...';\n    document.getElementById('sigint-results').innerHTML = '<div class=\"mono h-cyan\" style=\"text-align:center; padding:40px;\">ANALYZING CHATTER...</div>';\n    try {\n        const res = await fetch('/api/sigint', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ query })\n        });\n        const data = await res.json();\n        document.getElementById('sig-pos').textContent = data.sentiment_summary.POSITIVE;\n        document.getElementById('sig-neu').textContent = data.sentiment_summary.NEUTRAL;\n        document.getElementById('sig-neg').textContent = data.sentiment_summary.NEGATIVE;\n        let html = '';\n        data.feed.forEach(item => {\n            const sentClass = item.sentiment === 'POSITIVE' ? 'verified' : item.sentiment === 'NEGATIVE' ? 'critical' : 'medium';\n            html += `\n            <div class=\"result-card\" style=\"cursor:pointer;\" onclick=\"window.open('${item.url}')\">\n                <div class=\"spread\" style=\"margin-bottom:8px;\">\n                    <span class=\"tag\">[${item.source.toUpperCase()}] ${item.outlet || item.subreddit || ''}</span>\n                    <span class=\"dim mono\" style=\"font-size:0.74rem;\">${item.date || ''}</span>\n                </div>\n                <h3 class=\"card-title\" style=\"font-family:var(--font-ui); font-weight:600;\">${item.title}</h3>\n                <div class=\"dim\" style=\"font-size:0.86rem; margin:8px 0;\">${item.snippet || ''}</div>\n                <div class=\"row\" style=\"gap:10px; margin-top:6px;\">\n                    <span class=\"status-badge ${sentClass}\">${item.sentiment}</span>\n                    ${item.score ? `<span class=\"dim mono\" style=\"font-size:0.74rem;\">SCORE: ${item.score}</span>` : ''}\n                </div>\n            </div>`;\n        });\n        document.getElementById('sigint-results').innerHTML = html || '<div class=\"dim mono\" style=\"text-align:center;\">NO RECENT INTEL CAPTURED.</div>';\n        document.getElementById('sigint-status').textContent = `GATHERED ${data.total} INTELLIGENCE PACKETS.`;\n        refreshFX(document.getElementById('sigint-results'));\n    } catch (e) { document.getElementById('sigint-status').textContent = 'INTERCEPTION FAILURE.'; }\n}\n\n/* ============================================================\n   SHADOW MAP\n   ============================================================ */\nasync function scanShadow() {\n    const target = document.getElementById('shadow-input').value.trim();\n    if (!target) return;\n    document.getElementById('shadow-status').textContent = 'TRACING SHADOW INFRASTRUCTURE...';\n    document.getElementById('shadow-dashboard').classList.add('hide');\n    try {\n        const res = await fetch('/api/shadowmap', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ target })\n        });\n        const data = await res.json();\n        const d = document.getElementById('shadow-dashboard');\n        d.classList.remove('hide');\n        d.style.display = 'grid';\n\n        document.getElementById('shadow-score').textContent = data.threat_score;\n        document.getElementById('shadow-level').textContent = data.threat_level;\n        document.getElementById('shadow-level').className = 'status-badge ' + (\n            data.threat_level === 'CRITICAL' ? 'critical' :\n            data.threat_level === 'HIGH' ? 'high' :\n            data.threat_level === 'MEDIUM' ? 'medium' :\n            data.threat_level === 'LOW' ? 'low' : 'clean'\n        );\n\n        document.getElementById('shadow-factors').innerHTML = (data.threat_factors || [])\n            .map(f => `<div class=\"result-card\" style=\"padding:8px 12px; border-color:var(--red);\">⚠ ${f}</div>`)\n            .join('') || '<div class=\"dim mono\">NO ACTIVE THREATS REPORTED.</div>';\n\n        const abuse = data.abuseipdb;\n        document.getElementById('shadow-abuse').innerHTML = `<h3 class=\"card-title h-amber\">ABUSE IPDB</h3>${\n            abuse && abuse.available ? renderDict({\n                'CONFIDENCE': abuse.abuse_score + '%',\n                'TOTAL REPORTS': abuse.total_reports,\n                'ISP': abuse.isp || 'N/A',\n                'USAGE': abuse.usage_type || 'N/A'\n            }) : '<div class=\"dim\">[NO DATA OR API KEY MISSING]</div>'\n        }`;\n        const vt = data.virustotal;\n        document.getElementById('shadow-vt').innerHTML = `<h3 class=\"card-title h-green\">VIRUSTOTAL</h3>${\n            vt && vt.available ? renderDict({\n                MALICIOUS: vt.malicious, SUSPICIOUS: vt.suspicious,\n                REPUTATION: vt.reputation, 'TOTAL ENGINES': vt.total_engines\n            }) : '<div class=\"dim\">[NO DATA OR API KEY MISSING]</div>'\n        }`;\n        const geo = data.geo || data.ipinfo;\n        document.getElementById('shadow-geo').innerHTML = `<h3 class=\"card-title h-cyan\">TELEMETRY</h3>${\n            geo && Object.keys(geo).length ? renderDict({\n                IP: geo.ip, HOSTNAME: geo.hostname || 'None', CITY: geo.city,\n                COUNTRY: geo.country, ORGANIZATION: geo.org\n            }) : '<div class=\"dim\">[UNABLE TO RESOLVE LOCATION]</div>'\n        }`;\n        document.getElementById('shadow-status').textContent = 'TRACE COMPLETE.';\n        refreshFX(d);\n    } catch (e) { document.getElementById('shadow-status').textContent = 'TRACE ABORTED.'; }\n}\n\n/* ============================================================\n   ──── NEW V5 MODULES ─────────────────────────────────────────\n   ============================================================ */\n\n/* ---------- DOMAIN ORACLE ---------- */\nasync function scanOracle() {\n    const domain = document.getElementById('oracle-input').value.trim();\n    if (!domain) return;\n    document.getElementById('oracle-status').textContent = 'INVOKING THE ORACLE...';\n    document.getElementById('oracle-results').innerHTML = '<div class=\"mono h-cyan\" style=\"text-align:center; padding:40px;\">EXTRACTING DOMAIN INTELLIGENCE...</div>';\n    try {\n        const res = await fetch('/api/oracle', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ domain })\n        });\n        const data = await res.json();\n        if (data.error) { document.getElementById('oracle-status').textContent = data.error; document.getElementById('oracle-results').innerHTML = ''; return; }\n\n        const es = data.email_security || {};\n        const http = data.http_security || {};\n        const tls = data.tls || {};\n        const rdap = data.rdap || {};\n\n        const score = data.score || 0;\n        const scoreLevel = score >= 80 ? 'verified' : score >= 60 ? 'low' : score >= 40 ? 'medium' : score >= 20 ? 'high' : 'critical';\n\n        let html = `\n        <div class=\"result-card\">\n            <div class=\"spread\" style=\"margin-bottom:12px;\">\n                <h3 class=\"card-title h-cyan\">${data.domain} · ${data.ip}</h3>\n                <span class=\"status-badge ${scoreLevel}\">HYGIENE ${score}/100</span>\n            </div>\n            <div class=\"score-shell\" style=\"margin-bottom:12px;\">\n                <div class=\"score-meta\"><span>SECURITY POSTURE</span><strong>${score}/100</strong></div>\n                <div class=\"score-bar\"><div class=\"score-fill\" style=\"width:${score}%; background:var(--grad-ok);\"></div></div>\n            </div>\n        </div>\n\n        <div class=\"results\" style=\"grid-template-columns: 1fr 1fr; gap:14px;\">\n            <div class=\"result-card\">\n                <h3 class=\"card-title\">EMAIL SECURITY</h3>\n                <div class=\"row\" style=\"gap:6px; margin-bottom:8px;\">\n                    <span class=\"status-badge ${gradeBadgeClass(es.spf && es.spf.grade)}\">SPF · ${es.spf && es.spf.grade || 'F'}</span>\n                    <span class=\"status-badge ${gradeBadgeClass(es.dmarc && es.dmarc.grade)}\">DMARC · ${es.dmarc && es.dmarc.grade || 'F'}</span>\n                    <span class=\"status-badge ${gradeBadgeClass(es.dkim && es.dkim.grade)}\">DKIM · ${es.dkim && es.dkim.grade || 'F'}</span>\n                </div>\n                ${es.spf && es.spf.present ? `<div class=\"dim mono\" style=\"font-size:0.74rem;\">SPF policy: ${es.spf.policy}</div>` : '<div class=\"h-red mono\" style=\"font-size:0.74rem;\">No SPF — domain is spoofable</div>'}\n                ${es.dmarc && es.dmarc.present ? `<div class=\"dim mono\" style=\"font-size:0.74rem;\">DMARC policy: ${es.dmarc.policy}</div>` : '<div class=\"h-red mono\" style=\"font-size:0.74rem;\">No DMARC — no enforcement</div>'}\n                ${es.dkim && es.dkim.present ? `<div class=\"dim mono\" style=\"font-size:0.74rem;\">DKIM selectors: ${es.dkim.selectors_found.join(', ')}</div>` : '<div class=\"dim mono\" style=\"font-size:0.74rem;\">No DKIM selectors auto-discovered</div>'}\n            </div>\n            <div class=\"result-card\">\n                <h3 class=\"card-title\">HTTP SECURITY HEADERS</h3>\n                <div class=\"row\" style=\"gap:6px; margin-bottom:8px;\">\n                    <span class=\"status-badge ${gradeBadgeClass(http.grade)}\">GRADE · ${http.grade || 'F'}</span>\n                    <span class=\"tag\">${http.https_reachable ? 'HTTPS OK' : 'NO HTTPS'}</span>\n                </div>\n                <div class=\"dim mono\" style=\"font-size:0.74rem; margin-bottom:6px;\">Server: ${http.server || '—'} · Powered: ${http.powered_by || '—'}</div>\n                <div>${(http.missing || []).map(h => `<span class=\"tag hot\">missing: ${h}</span>`).join('') || '<span class=\"tag ok\">all headers present</span>'}</div>\n            </div>\n            <div class=\"result-card\">\n                <h3 class=\"card-title\">REGISTRATION</h3>\n                ${renderDict({\n                    REGISTRAR: rdap.registrar,\n                    CREATED: rdap.created,\n                    EXPIRES: rdap.expires,\n                    UPDATED: rdap.updated,\n                    STATUS: (rdap.status || []).join(', ') || '—'\n                })}\n            </div>\n            <div class=\"result-card\">\n                <h3 class=\"card-title\">TLS</h3>\n                ${tls.error ? `<div class=\"h-red mono\">${tls.error}</div>` : renderDict({\n                    VERSION: tls.tls_version, CIPHER: tls.cipher,\n                    NOT_BEFORE: tls.not_before, NOT_AFTER: tls.not_after,\n                    ISSUER_CN: tls.issuer && tls.issuer.commonName,\n                    SUBJECT_CN: tls.subject && tls.subject.commonName\n                })}\n            </div>\n        </div>\n\n        <div class=\"result-card\" style=\"margin-top:14px;\">\n            <h3 class=\"card-title\">DNS RECORDS</h3>\n            ${Object.entries(data.dns || {}).map(([k, v]) =>\n                v && v.length ? `<div style=\"margin-bottom:6px;\"><span class=\"tag\">${k}</span> <span class=\"mono\" style=\"font-size:0.78rem;\">${v.join(', ').slice(0, 600)}</span></div>` : ''\n            ).join('')}\n        </div>\n\n        <div class=\"result-card\" style=\"margin-top:14px;\">\n            <h3 class=\"card-title\">SUBDOMAINS (crt.sh) · ${(data.subdomains || []).length}</h3>\n            <div style=\"max-height:200px; overflow-y:auto;\">\n                ${(data.subdomains || []).slice(0, 200).map(s => `<span class=\"tag\">${s}</span>`).join('') || '<div class=\"dim\">none discovered</div>'}\n            </div>\n        </div>\n\n        ${data.neighbors && data.neighbors.length ? `\n        <div class=\"result-card\" style=\"margin-top:14px;\">\n            <h3 class=\"card-title\">REVERSE-IP NEIGHBORS · ${data.neighbors.length}</h3>\n            <div style=\"max-height:200px; overflow-y:auto;\">\n                ${data.neighbors.slice(0, 50).map(n => `<span class=\"tag\">${n}</span>`).join('')}\n            </div>\n        </div>` : ''}\n        `;\n        document.getElementById('oracle-results').innerHTML = html;\n        document.getElementById('oracle-status').textContent = 'ORACLE COMPLETE.';\n        refreshFX(document.getElementById('oracle-results'));\n    } catch (e) {\n        console.error(e);\n        document.getElementById('oracle-status').textContent = 'ORACLE CHANNEL CLOSED.';\n    }\n}\n\n/* ---------- MAIL TRACER ---------- */\nasync function scanMail() {\n    const email = document.getElementById('mail-input').value.trim();\n    if (!email) return;\n    document.getElementById('mail-status').textContent = 'TRACING MAIL INFRASTRUCTURE...';\n    document.getElementById('mail-results').innerHTML = '<div class=\"mono h-cyan\" style=\"text-align:center; padding:40px;\">QUERYING DNS · GRAVATAR · DMARC...</div>';\n    try {\n        const res = await fetch('/api/mailtracer', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ email })\n        });\n        const data = await res.json();\n        if (data.error) { document.getElementById('mail-status').textContent = data.error; document.getElementById('mail-results').innerHTML = ''; return; }\n        const level = data.trust_score >= 80 ? 'verified' : data.trust_score >= 60 ? 'low' : data.trust_score >= 40 ? 'medium' : 'critical';\n        const es = data.email_security || {};\n        document.getElementById('mail-results').innerHTML = `\n        <div class=\"result-card\">\n            <div class=\"spread\" style=\"margin-bottom:12px;\">\n                <h3 class=\"card-title h-cyan\">${data.email}</h3>\n                <span class=\"status-badge ${level}\">${data.trust_level} · ${data.trust_score}/100</span>\n            </div>\n            <div class=\"score-shell\" style=\"margin-bottom:14px;\">\n                <div class=\"score-meta\"><span>TRUST SCORE</span><strong>${data.trust_score}/100</strong></div>\n                <div class=\"score-bar\"><div class=\"score-fill\" style=\"width:${data.trust_score}%; background:var(--grad-ok);\"></div></div>\n            </div>\n            <div class=\"row\" style=\"gap:6px; margin-bottom:12px;\">\n                <span class=\"tag ${data.flags.deliverable ? 'ok' : 'hot'}\">${data.flags.deliverable ? 'DELIVERABLE' : 'NO MX'}</span>\n                ${data.flags.disposable ? '<span class=\"tag hot\">DISPOSABLE</span>' : ''}\n                ${data.flags.role_based ? '<span class=\"tag warn\">ROLE-BASED</span>' : ''}\n                ${data.flags.free_provider ? '<span class=\"tag\">FREE PROVIDER</span>' : ''}\n            </div>\n            <div class=\"row\" style=\"gap:6px; margin-bottom:14px;\">\n                <span class=\"status-badge ${gradeBadgeClass(es.spf && es.spf.grade)}\">SPF · ${es.spf && es.spf.grade || 'F'}</span>\n                <span class=\"status-badge ${gradeBadgeClass(es.dmarc && es.dmarc.grade)}\">DMARC · ${es.dmarc && es.dmarc.grade || 'F'}</span>\n            </div>\n            ${data.gravatar && data.gravatar.exists ? `<div class=\"row\" style=\"margin-bottom:12px; align-items:center;\">\n                <img src=\"${data.gravatar.url}\" style=\"width:64px; height:64px; border-radius:50%; border:1px solid var(--cyan);\">\n                <span class=\"dim mono\">Gravatar identity bound</span>\n            </div>` : ''}\n            <div>\n                <h4 class=\"mono dim\" style=\"margin:0 0 6px;\">MX RECORDS</h4>\n                ${(data.mx || []).map(m => `<span class=\"tag\">${m}</span>`).join('') || '<span class=\"tag hot\">none</span>'}\n            </div>\n            <div style=\"margin-top:14px;\">\n                <h4 class=\"mono dim\" style=\"margin:0 0 6px;\">SIGNAL FACTORS</h4>\n                ${data.factors.map(f => `<div class=\"dim mono\" style=\"font-size:0.78rem;\">• ${f}</div>`).join('')}\n            </div>\n        </div>`;\n        document.getElementById('mail-status').textContent = 'TRACE COMPLETE.';\n        refreshFX(document.getElementById('mail-results'));\n    } catch (e) {\n        document.getElementById('mail-status').textContent = 'MAIL TRACER FAILED.';\n    }\n}\n\n/* ---------- CODE HUNTER ---------- */\nasync function scanCode() {\n    const username = document.getElementById('code-input').value.trim();\n    if (!username) return;\n    document.getElementById('code-status').textContent = 'PROBING GITHUB...';\n    document.getElementById('code-results').innerHTML = '<div class=\"mono h-cyan\" style=\"text-align:center; padding:40px;\">QUERYING GITHUB API...</div>';\n    try {\n        const res = await fetch('/api/codehunter', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ username })\n        });\n        const data = await res.json();\n        if (data.error) { document.getElementById('code-status').textContent = data.error; document.getElementById('code-results').innerHTML = ''; return; }\n\n        const maxLang = Math.max(1, ...(data.languages || []).map(l => l.count));\n        const maxHour = Math.max(1, ...(data.activity?.hourly_utc || [1]));\n\n        document.getElementById('code-results').innerHTML = `\n        <div class=\"result-card\">\n            <div class=\"row\" style=\"align-items:flex-start; gap:16px;\">\n                <img src=\"${data.avatar}\" style=\"width:96px; height:96px; border-radius:50%; border:2px solid var(--cyan); box-shadow:0 0 20px rgba(0,240,255,0.5);\">\n                <div style=\"flex:1;\">\n                    <div class=\"spread\" style=\"margin-bottom:6px;\">\n                        <h3 class=\"card-title h-cyan\">${data.name || data.login} · <span class=\"dim\">@${data.login}</span></h3>\n                        <span class=\"status-badge verified\">INFLUENCE ${data.influence_score}</span>\n                    </div>\n                    <div class=\"dim\" style=\"font-size:0.86rem; margin-bottom:8px;\">${data.bio || ''}</div>\n                    <div class=\"row\" style=\"gap:6px; flex-wrap:wrap;\">\n                        ${data.company ? `<span class=\"tag\">🏢 ${data.company}</span>` : ''}\n                        ${data.location ? `<span class=\"tag\">📍 ${data.location}</span>` : ''}\n                        ${data.blog ? `<span class=\"tag\">🔗 <a href=\"${data.blog}\" target=\"_blank\" style=\"color:inherit; text-decoration:none;\">${data.blog}</a></span>` : ''}\n                        ${data.twitter ? `<span class=\"tag\">🐦 ${data.twitter}</span>` : ''}\n                        ${data.email_public ? `<span class=\"tag hot\">📧 ${data.email_public}</span>` : ''}\n                    </div>\n                </div>\n            </div>\n            <div class=\"row\" style=\"margin-top:14px; gap:18px; flex-wrap:wrap;\">\n                <div><div class=\"dim mono\" style=\"font-size:0.7rem;\">FOLLOWERS</div><div style=\"font-size:1.4rem; font-family:var(--font-display);\">${data.followers}</div></div>\n                <div><div class=\"dim mono\" style=\"font-size:0.7rem;\">FOLLOWING</div><div style=\"font-size:1.4rem; font-family:var(--font-display);\">${data.following}</div></div>\n                <div><div class=\"dim mono\" style=\"font-size:0.7rem;\">REPOS</div><div style=\"font-size:1.4rem; font-family:var(--font-display);\">${data.public_repos}</div></div>\n                <div><div class=\"dim mono\" style=\"font-size:0.7rem;\">⭐ STARS</div><div style=\"font-size:1.4rem; font-family:var(--font-display);\">${data.total_stars}</div></div>\n                <div><div class=\"dim mono\" style=\"font-size:0.7rem;\">GISTS</div><div style=\"font-size:1.4rem; font-family:var(--font-display);\">${data.gist_count}</div></div>\n                <div><div class=\"dim mono\" style=\"font-size:0.7rem;\">CREATED</div><div class=\"mono\" style=\"font-size:0.86rem;\">${(data.created_at || '').split('T')[0]}</div></div>\n            </div>\n        </div>\n\n        ${(data.commit_emails && data.commit_emails.length) ? `\n        <div class=\"result-card\" style=\"margin-top:14px;\">\n            <h3 class=\"card-title h-red\">COMMIT EMAILS HARVESTED · ${data.commit_emails.length}</h3>\n            ${data.commit_emails.map(e => `<div class=\"row\" style=\"justify-content:space-between; padding:4px 0;\"><span class=\"mono\">${e.email}</span><span class=\"tag hot\">${e.count}×</span></div>`).join('')}\n        </div>` : ''}\n\n        <div class=\"results\" style=\"grid-template-columns: 1fr 1fr; gap:14px; margin-top:14px;\">\n            <div class=\"result-card\">\n                <h3 class=\"card-title\">LANGUAGES</h3>\n                ${(data.languages || []).map(l => `\n                    <div style=\"margin-bottom:6px;\">\n                        <div class=\"row\" style=\"justify-content:space-between; font-size:0.78rem;\"><span>${l.name}</span><span class=\"dim mono\">${l.count}</span></div>\n                        <div class=\"mini-bar\"><div style=\"width:${(l.count / maxLang) * 100}%;\"></div></div>\n                    </div>`).join('') || '<div class=\"dim\">No languages</div>'}\n            </div>\n            <div class=\"result-card\">\n                <h3 class=\"card-title\">ACTIVITY · HOURLY (UTC)</h3>\n                <div class=\"row\" style=\"gap:2px; align-items:flex-end; height:80px; margin-bottom:6px;\">\n                    ${(data.activity?.hourly_utc || []).map(h => `<div style=\"flex:1; background:var(--grad-cool); height:${(h / maxHour) * 100}%; min-height:2px; border-radius:2px;\" title=\"${h} events\"></div>`).join('')}\n                </div>\n                <div class=\"row dim mono\" style=\"font-size:0.66rem; justify-content:space-between;\"><span>00h</span><span>12h</span><span>23h</span></div>\n            </div>\n        </div>\n\n        <div class=\"result-card\" style=\"margin-top:14px;\">\n            <h3 class=\"card-title\">TOP REPOSITORIES</h3>\n            <div class=\"results\" style=\"grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap:10px;\">\n                ${(data.top_repos || []).map(r => `\n                <div class=\"result-card\" style=\"border-color:rgba(0,240,255,0.25);\">\n                    <div class=\"spread\" style=\"margin-bottom:6px;\">\n                        <a href=\"${r.url}\" target=\"_blank\" class=\"card-url\" style=\"margin:0;\">${r.name}</a>\n                        <span class=\"tag\">⭐ ${r.stars}</span>\n                    </div>\n                    <div class=\"dim\" style=\"font-size:0.78rem; margin-bottom:6px;\">${r.description || ''}</div>\n                    <div class=\"row\" style=\"gap:6px;\">${r.language ? `<span class=\"tag\">${r.language}</span>` : ''}${r.fork ? '<span class=\"tag warn\">FORK</span>' : ''}</div>\n                </div>`).join('')}\n            </div>\n        </div>\n\n        ${(data.orgs && data.orgs.length) ? `\n        <div class=\"result-card\" style=\"margin-top:14px;\">\n            <h3 class=\"card-title\">ORGANIZATIONS</h3>\n            <div>${data.orgs.map(o => `<span class=\"tag\">${o.login}</span>`).join('')}</div>\n        </div>` : ''}\n        `;\n        document.getElementById('code-status').textContent = 'HUNT COMPLETE.';\n        refreshFX(document.getElementById('code-results'));\n    } catch (e) {\n        document.getElementById('code-status').textContent = 'GITHUB QUERY FAILED.';\n    }\n}\n\n/* ---------- WAYBACK SPECTRE ---------- */\nasync function scanWayback() {\n    const target = document.getElementById('wayback-input').value.trim();\n    if (!target) return;\n    document.getElementById('wayback-status').textContent = 'SUMMONING WAYBACK MACHINE...';\n    document.getElementById('wayback-results').innerHTML = '<div class=\"mono h-cyan\" style=\"text-align:center; padding:40px;\">TRAVERSING ARCHIVED TIMELINE...</div>';\n    try {\n        const res = await fetch('/api/wayback', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ target })\n        });\n        const data = await res.json();\n        if (data.error) { document.getElementById('wayback-status').textContent = data.error; document.getElementById('wayback-results').innerHTML = ''; return; }\n        if (!data.total_snapshots) {\n            document.getElementById('wayback-results').innerHTML = `<div class=\"result-card\" style=\"text-align:center;\"><h3 class=\"card-title dim\">NO SNAPSHOTS</h3><div class=\"dim\">${data.message || 'Wayback Machine has no records.'}</div></div>`;\n            document.getElementById('wayback-status').textContent = 'TIMELINE EMPTY.';\n            return;\n        }\n        const maxY = Math.max(...data.yearly.map(y => y.count), 1);\n\n        document.getElementById('wayback-results').innerHTML = `\n        <div class=\"result-card\">\n            <div class=\"spread\" style=\"margin-bottom:12px;\">\n                <h3 class=\"card-title h-cyan\">${data.target}</h3>\n                <span class=\"status-badge info\">${data.total_snapshots.toLocaleString()} SNAPSHOTS</span>\n            </div>\n            <div class=\"row\" style=\"gap:18px; flex-wrap:wrap;\">\n                <div><div class=\"dim mono\" style=\"font-size:0.7rem;\">FIRST SEEN</div><div class=\"mono\">${data.first_seen}</div></div>\n                <div><div class=\"dim mono\" style=\"font-size:0.7rem;\">LAST SEEN</div><div class=\"mono\">${data.last_seen}</div></div>\n                <div><div class=\"dim mono\" style=\"font-size:0.7rem;\">SENSITIVE PATHS</div><div class=\"mono h-red\" style=\"font-size:1.2rem;\">${data.sensitive_count}</div></div>\n            </div>\n        </div>\n\n        <div class=\"result-card\" style=\"margin-top:14px;\">\n            <h3 class=\"card-title\">YEARLY DISTRIBUTION</h3>\n            <div class=\"row\" style=\"gap:4px; align-items:flex-end; height:120px; margin-bottom:6px;\">\n                ${data.yearly.map(y => `<div style=\"flex:1; background:var(--grad-cool); height:${(y.count / maxY) * 100}%; min-height:3px; border-radius:3px;\" title=\"${y.year}: ${y.count}\"></div>`).join('')}\n            </div>\n            <div class=\"row dim mono\" style=\"font-size:0.66rem; justify-content:space-between;\">\n                <span>${data.yearly[0].year}</span><span>${data.yearly[data.yearly.length - 1].year}</span>\n            </div>\n        </div>\n\n        ${data.sensitive.length ? `\n        <div class=\"result-card\" style=\"margin-top:14px;\">\n            <h3 class=\"card-title h-red\">⚠ SENSITIVE PATHS DISCOVERED · ${data.sensitive.length}</h3>\n            ${data.sensitive.map(s => `\n                <div class=\"row\" style=\"justify-content:space-between; padding:6px 0; border-bottom:1px solid rgba(255,255,255,0.04);\">\n                    <div style=\"flex:1;\">\n                        <span class=\"tag hot\">${s.pattern}</span>\n                        <a href=\"${s.snapshot}\" target=\"_blank\" class=\"card-url\" style=\"margin:4px 0;\">${s.url}</a>\n                    </div>\n                    <span class=\"dim mono\" style=\"font-size:0.74rem;\">${s.timestamp}</span>\n                </div>`).join('')}\n        </div>` : ''}\n\n        <div class=\"result-card\" style=\"margin-top:14px;\">\n            <h3 class=\"card-title\">SAMPLE SNAPSHOTS · ${data.sample.length}</h3>\n            ${data.sample.slice(0, 25).map(s => `\n                <div class=\"row\" style=\"justify-content:space-between; padding:4px 0; font-size:0.78rem;\">\n                    <a href=\"${s.snapshot}\" target=\"_blank\" class=\"card-url\" style=\"margin:0; flex:1;\">${s.url}</a>\n                    <span class=\"dim mono\" style=\"font-size:0.72rem;\">${s.timestamp}</span>\n                </div>`).join('')}\n        </div>`;\n        document.getElementById('wayback-status').textContent = `TIMELINE TRAVERSED · ${data.total_snapshots} CAPTURES.`;\n        refreshFX(document.getElementById('wayback-results'));\n    } catch (e) {\n        document.getElementById('wayback-status').textContent = 'TIME-MACHINE OFFLINE.';\n    }\n}\n\n/* ---------- PASTE DRAGNET ---------- */\nasync function scanPaste() {\n    const query = document.getElementById('paste-input').value.trim();\n    if (!query) return;\n    document.getElementById('paste-status').textContent = 'DEPLOYING DRAGNET ACROSS PASTE NETWORKS...';\n    document.getElementById('paste-results').innerHTML = '<div class=\"mono h-cyan\" style=\"text-align:center; padding:40px;\">SCRAPING DUMP SITES...</div>';\n    document.getElementById('paste-summary').innerHTML = '';\n    try {\n        const res = await fetch('/api/paste', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ query })\n        });\n        const data = await res.json();\n        if (data.error) { document.getElementById('paste-status').textContent = data.error; document.getElementById('paste-results').innerHTML = ''; return; }\n        const sites = Object.entries(data.per_site || {});\n        document.getElementById('paste-summary').innerHTML = `\n        <div class=\"result-card\" style=\"margin-bottom:14px;\">\n            <h3 class=\"card-title\">DRAGNET REPORT · ${data.total_hits} TOTAL HITS</h3>\n            <div class=\"row\" style=\"gap:6px; flex-wrap:wrap; margin-top:8px;\">\n                ${sites.map(([site, count]) => `<span class=\"tag ${count > 0 ? 'hot' : ''}\">${site}: ${count}</span>`).join('')}\n            </div>\n        </div>`;\n        if (!data.results.length) {\n            document.getElementById('paste-results').innerHTML = '<div class=\"result-card\" style=\"text-align:center;\"><h3 class=\"card-title dim\">CLEAN</h3><div class=\"dim\">No paste-site references found.</div></div>';\n        } else {\n            document.getElementById('paste-results').innerHTML = data.results.map(r => `\n            <div class=\"result-card\">\n                <div class=\"spread\" style=\"margin-bottom:8px;\">\n                    <h3 class=\"card-title\">${r.title || '(no title)'}</h3>\n                    <span class=\"status-badge ${r.severity === 'CRITICAL' ? 'critical' : 'low'}\">${r.severity}</span>\n                </div>\n                <div class=\"row\" style=\"gap:6px; margin-bottom:6px;\"><span class=\"tag\">${r.site}</span></div>\n                <div class=\"dim\" style=\"font-size:0.84rem; margin-bottom:8px;\">${r.snippet || ''}</div>\n                <a href=\"${r.url}\" target=\"_blank\" class=\"card-url\">${r.url}</a>\n            </div>`).join('');\n        }\n        document.getElementById('paste-status').textContent = `DRAGNET COMPLETE · ${data.total_hits} HITS.`;\n        refreshFX(document.getElementById('paste-results'));\n    } catch (e) {\n        document.getElementById('paste-status').textContent = 'DRAGNET FAILED.';\n    }\n}\n\n/* ---------- AI ANALYST ---------- */\nasync function runAnalyst() {\n    const target = document.getElementById('analyst-input').value.trim();\n    const mode = document.getElementById('analyst-mode').value;\n    if (!target) return;\n    document.getElementById('analyst-status').textContent = 'ORCHESTRATING MODULES — THIS MAY TAKE 30-60s...';\n    document.getElementById('analyst-summary').classList.add('hide');\n    document.getElementById('analyst-findings').classList.add('hide');\n    document.getElementById('analyst-modules').classList.add('hide');\n    try {\n        const res = await fetch('/api/analyst', {\n            method: 'POST', headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ target, mode })\n        });\n        const data = await res.json();\n        if (data.error) { document.getElementById('analyst-status').textContent = data.error; return; }\n\n        const verdictClass = data.verdict === 'CRITICAL' ? 'critical' : data.verdict === 'HIGH' ? 'high' : data.verdict === 'MODERATE' ? 'medium' : 'low';\n\n        const summary = document.getElementById('analyst-summary');\n        summary.classList.remove('hide');\n        summary.innerHTML = `\n        <div class=\"hud-panel reveal\" style=\"margin-top:14px;\">\n            <span class=\"bracket tl\"></span><span class=\"bracket tr\"></span>\n            <span class=\"bracket bl\"></span><span class=\"bracket br\"></span>\n            <div class=\"spread\" style=\"margin-bottom:12px;\">\n                <div>\n                    <div class=\"panel-title\" style=\"margin:0;\">UNIFIED THREAT PROFILE</div>\n                    <div class=\"dim mono\" style=\"font-size:0.74rem; margin-top:6px;\">${data.target} · detected as <span class=\"h-cyan\">${data.detected_type}</span></div>\n                </div>\n                <span class=\"status-badge ${verdictClass}\" style=\"font-size:0.78rem; padding:6px 14px;\">${data.verdict} · ${data.risk_score}/100</span>\n            </div>\n            <div class=\"score-shell\">\n                <div class=\"score-meta\"><span>AGGREGATE RISK</span><strong>${data.risk_score}/100</strong></div>\n                <div class=\"score-bar\"><div class=\"score-fill\" style=\"width:${data.risk_score}%;\"></div></div>\n            </div>\n            <div class=\"row\" style=\"gap:6px; margin-top:12px; flex-wrap:wrap;\">\n                ${(data.modules_run || []).map(m => `<span class=\"tag ok\">✓ ${m}</span>`).join('')}\n            </div>\n        </div>`;\n\n        const findings = document.getElementById('analyst-findings');\n        findings.classList.remove('hide');\n        findings.innerHTML = `\n        <div class=\"hud-panel reveal\" style=\"margin-top:14px;\">\n            <div class=\"panel-title\">SIGNAL FINDINGS</div>\n            ${(data.findings || []).map((f, i) => `<div class=\"result-card\" style=\"margin-bottom:8px;\"><div class=\"row\" style=\"gap:10px;\"><span class=\"tag\">${String(i + 1).padStart(2, '0')}</span><span>${f}</span></div></div>`).join('')}\n        </div>`;\n\n        const modules = document.getElementById('analyst-modules');\n        modules.classList.remove('hide');\n        modules.innerHTML = `\n        <div class=\"hud-panel reveal\" style=\"margin-top:14px;\">\n            <div class=\"panel-title\">MODULE PAYLOADS (RAW)</div>\n            ${Object.entries(data.raw || {}).map(([name, payload]) => `\n                <details class=\"result-card\" style=\"margin-bottom:8px;\">\n                    <summary class=\"card-title\" style=\"cursor:pointer;\">${name} ${payload && payload.error ? '<span class=\"status-badge critical\" style=\"margin-left:8px;\">ERROR</span>' : '<span class=\"status-badge verified\" style=\"margin-left:8px;\">OK</span>'}</summary>\n                    <pre class=\"mono dim\" style=\"margin-top:10px; max-height:300px; overflow:auto; font-size:0.72rem; padding:10px; background:rgba(0,0,0,0.4); border-radius:6px;\">${JSON.stringify(payload, null, 2)}</pre>\n                </details>`).join('')}\n        </div>`;\n        document.getElementById('analyst-status').textContent = `SYNTHESIS COMPLETE · ${data.verdict}`;\n        refreshFX(summary); refreshFX(findings); refreshFX(modules);\n    } catch (e) {\n        console.error(e);\n        document.getElementById('analyst-status').textContent = 'ANALYST OFFLINE.';\n    }\n}\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "the_big_brother/image_grabber.py",
    "content": "from duckduckgo_search import DDGS\nfrom playwright.sync_api import sync_playwright\nimport time\nimport random\n\ndef fetch_images_google_playwright(query: str, limit: int = 3, headless: bool = True) -> list[str]:\n    \"\"\"Fallback: Fetch images using Playwright (Google Images)\"\"\"\n    print(f\"   [+] Attempting Google Images for {query}...\")\n    try:\n        with sync_playwright() as p:\n            browser = p.chromium.launch(headless=headless)\n            context = browser.new_context(\n                user_agent=\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\",\n                viewport={\"width\": 1920, \"height\": 1080},\n                locale=\"en-US\"\n            )\n            page = context.new_page()\n            \n            # Google Images Search with SafeSearch OFF\n            page.goto(f\"https://www.google.com/search?tbm=isch&q={query}&safe=off\", timeout=15000)\n            \n            # Human-like delay\n            time.sleep(random.uniform(1.5, 3.0))\n\n            # Accept cookies if needed\n            try:\n                page.click(\"button:has-text('Reject all')\", timeout=2000)\n            except: pass\n\n            images = page.evaluate(\"\"\"() => {\n                const imgs = Array.from(document.querySelectorAll('img'));\n                return imgs\n                    .map(img => img.src || img.getAttribute('data-src'))\n                    .filter(src => src && src.startsWith('http') && src.length > 50 && !src.includes('googleg') && !src.includes('.svg')) \n                    .slice(0, 5);\n            }\"\"\")\n            browser.close()\n            return images[:limit]\n    except Exception as e:\n        print(f\"   [-] Google Playwright error: {e}\")\n        return []\n\ndef fetch_images_bing_playwright(query: str, limit: int = 3, headless: bool = True) -> list[str]:\n    \"\"\"Fallback: Fetch images using Playwright (Bing Images)\"\"\"\n    print(f\"   [+] Attempting Bing Images for {query}...\")\n    try:\n        with sync_playwright() as p:\n            browser = p.chromium.launch(headless=headless)\n            context = browser.new_context(\n                user_agent=\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15\",\n                viewport={\"width\": 1920, \"height\": 1080}\n            )\n            page = context.new_page()\n            \n            # Bing Images with SafeSearch OFF\n            page.goto(f\"https://www.bing.com/images/search?q={query}&adlt=off\", timeout=15000)\n            time.sleep(random.uniform(1.0, 2.5))\n            \n            images = page.evaluate(\"\"\"() => {\n                const imgs = Array.from(document.querySelectorAll('.mimg'));\n                return imgs\n                    .map(img => img.src || img.getAttribute('data-src'))\n                    .filter(src => src && src.startsWith('http'))\n                    .slice(0, 5);\n            }\"\"\")\n            browser.close()\n            return images[:limit]\n    except Exception as e:\n        print(f\"   [-] Bing Playwright error: {e}\")\n        return []\n\ndef fetch_images(query: str, limit: int = 3) -> list[str]:\n    \"\"\"\n    Robust Multi-Engine Image Fetcher.\n    Strategy: DDGS (Fast) -> Bing (Medium) -> Google (Slow/Fallback).\n    \"\"\"\n    print(f\"[*] Starting Image Search for: {query}\")\n    return fetch_images_with_diag(query, limit)[0]\n\n\ndef fetch_images_with_diag(query: str, limit: int = 3):\n    \"\"\"\n    Same as fetch_images but also returns a diagnostic string describing\n    which engines were tried and how each failed. Returns (results, diag).\n    \"\"\"\n    diag_parts: list[str] = []\n\n    # 1. DuckDuckGo (no browser needed)\n    try:\n        print(\"   [+] Attempting DuckDuckGo...\")\n        time.sleep(random.uniform(0.5, 1.5))\n        with DDGS() as ddgs:\n            ddgs_images = list(ddgs.images(query, max_results=max(limit, 8)))\n            results = [r['image'] for r in ddgs_images if 'image' in r]\n            if results:\n                print(f\"   [+] DDGS Success: Found {len(results)} images.\")\n                return results[:limit], \"DDGS:ok\"\n            diag_parts.append(\"DDGS:empty\")\n    except Exception as e:\n        msg = str(e).splitlines()[0][:160]\n        print(f\"   [-] DDGS Failed ({msg}). Moving to next engine.\")\n        diag_parts.append(f\"DDGS:err({_short_err(msg)})\")\n\n    # 2. Bing (Playwright)\n    try:\n        results = fetch_images_bing_playwright(query, limit)\n        if results:\n            print(f\"   [+] Bing Success: Found {len(results)} images.\")\n            return results, \"Bing:ok\"\n        diag_parts.append(\"Bing:empty\")\n    except Exception as e:\n        diag_parts.append(f\"Bing:err({_short_err(str(e))})\")\n\n    # 3. Google (Playwright)\n    try:\n        results = fetch_images_google_playwright(query, limit)\n        if results:\n            print(f\"   [+] Google Success: Found {len(results)} images.\")\n            return results, \"Google:ok\"\n        diag_parts.append(\"Google:empty\")\n    except Exception as e:\n        diag_parts.append(f\"Google:err({_short_err(str(e))})\")\n\n    print(\"   [!] All image fetch methods failed.\")\n    return [], \" · \".join(diag_parts)\n\n\ndef _short_err(msg: str) -> str:\n    \"\"\"Map common low-level error strings to human-readable tags.\"\"\"\n    s = (msg or \"\").lower()\n    if \"executable doesn't exist\" in s or \"chromium\" in s and \"install\" in s:\n        return \"playwright-chromium-missing (run: playwright install chromium)\"\n    if \"ratelimit\" in s or \"rate-limit\" in s or \"202 ratelimit\" in s or \"too many\" in s:\n        return \"rate-limited\"\n    if \"timeout\" in s:\n        return \"timeout\"\n    if \"executable\" in s and \"doesn't exist\" in s:\n        return \"browser-missing\"\n    return (msg[:80] + \"...\") if len(msg) > 80 else msg\n\nif __name__ == \"__main__\":\n    print(fetch_images(\"chadi0x\"))\n"
  },
  {
    "path": "the_big_brother/modules/__init__.py",
    "content": ""
  },
  {
    "path": "the_big_brother/modules/ai_analyst.py",
    "content": "\"\"\"\nAI ANALYST — Cross-module orchestrator.\nTakes a single target + auto-detected type, fans out to the relevant modules\nin parallel, and synthesizes a unified threat profile with a 0-100 score and\nnarrative bullets. Pure heuristic synthesis — no external LLM required.\n\"\"\"\nimport asyncio\nimport re\n\nfrom the_big_brother.modules.phantom_id import phantom_id_search\nfrom the_big_brother.modules.breach_vault import breach_vault_search\nfrom the_big_brother.modules.sigint_sweep import sigint_sweep\nfrom the_big_brother.modules.shadow_map import shadow_map_analyze\nfrom the_big_brother.modules.domain_oracle import domain_oracle\nfrom the_big_brother.modules.mail_tracer import mail_tracer\nfrom the_big_brother.modules.code_hunter import code_hunter\nfrom the_big_brother.modules.wayback_spectre import wayback_spectre\nfrom the_big_brother.modules.paste_dragnet import paste_dragnet\nfrom the_big_brother.modules.digital_footprint import run_holehe\n\n\nEMAIL_RE = re.compile(r\"^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$\")\nDOMAIN_RE = re.compile(r\"^[a-z0-9.-]+\\.[a-z]{2,}$\", re.IGNORECASE)\nIP_RE = re.compile(r\"^\\d{1,3}(?:\\.\\d{1,3}){3}$\")\nUSERNAME_RE = re.compile(r\"^@?[a-z0-9_.-]{2,32}$\", re.IGNORECASE)\n\n\ndef detect_type(target: str) -> str:\n    t = target.strip()\n    if EMAIL_RE.match(t):\n        return \"email\"\n    if IP_RE.match(t):\n        return \"ip\"\n    if DOMAIN_RE.match(t):\n        return \"domain\"\n    if USERNAME_RE.match(t):\n        return \"username\"\n    return \"unknown\"\n\n\nasync def _safe(coro, label):\n    try:\n        return label, await coro\n    except Exception as e:\n        return label, {\"error\": str(e)}\n\n\nasync def ai_analyst(target: str, mode: str = \"auto\") -> dict:\n    target = target.strip()\n    if not target:\n        return {\"error\": \"target required\"}\n\n    detected = detect_type(target) if mode == \"auto\" else mode\n    if detected == \"unknown\":\n        return {\"error\": \"Could not classify target. Try email, domain, IP, or username.\"}\n\n    plan = {\n        \"email\": [\"mail_tracer\", \"breach\", \"holehe\", \"paste\"],\n        \"username\": [\"phantom\", \"code_hunter\", \"paste\"],\n        \"domain\": [\"domain_oracle\", \"shadow_map\", \"wayback\", \"sigint\", \"paste\"],\n        \"ip\": [\"shadow_map\"],\n    }[detected]\n\n    coros = []\n    if \"phantom\" in plan:\n        coros.append(_safe(phantom_id_search(target.lstrip(\"@\")), \"phantom\"))\n    if \"breach\" in plan:\n        coros.append(_safe(breach_vault_search(target, \"email\"), \"breach\"))\n    if \"holehe\" in plan:\n        coros.append(_safe(run_holehe(target), \"holehe\"))\n    if \"shadow_map\" in plan:\n        coros.append(_safe(shadow_map_analyze(target), \"shadow_map\"))\n    if \"domain_oracle\" in plan:\n        coros.append(_safe(domain_oracle(target), \"domain_oracle\"))\n    if \"mail_tracer\" in plan:\n        coros.append(_safe(mail_tracer(target), \"mail_tracer\"))\n    if \"code_hunter\" in plan:\n        coros.append(_safe(code_hunter(target.lstrip(\"@\")), \"code_hunter\"))\n    if \"wayback\" in plan:\n        coros.append(_safe(wayback_spectre(target), \"wayback\"))\n    if \"sigint\" in plan:\n        coros.append(_safe(sigint_sweep(target), \"sigint\"))\n    if \"paste\" in plan:\n        coros.append(_safe(paste_dragnet(target), \"paste\"))\n\n    results = dict(await asyncio.gather(*coros))\n\n    findings = []\n    score = 0\n\n    p = results.get(\"phantom\") or {}\n    if p.get(\"found\"):\n        findings.append(f\"Found on {p.get('found')}/{p.get('total_checked')} platforms — risk {p.get('risk_score')}\")\n        score += min(30, p.get(\"risk_score\", 0) // 3)\n\n    b = results.get(\"breach\") or {}\n    if b.get(\"breach_count\"):\n        findings.append(f\"Exposed in {b.get('breach_count')} breaches · {b.get('total_records_exposed', 0):,} records\")\n        score += min(35, b.get(\"breach_count\", 0) * 4)\n    if b.get(\"pwned\"):\n        findings.append(f\"Password seen {b.get('count', 0):,} times in HaveIBeenPwned\")\n        score += 30\n\n    s = results.get(\"shadow_map\") or {}\n    if s.get(\"threat_score\"):\n        findings.append(f\"Threat score {s.get('threat_score')} · {s.get('threat_level')}\")\n        score += min(30, s.get(\"threat_score\", 0) // 3)\n\n    d = results.get(\"domain_oracle\") or {}\n    if d.get(\"score\") is not None:\n        findings.append(f\"Domain hygiene {d.get('score')}/100 — SPF/DMARC/DKIM/headers averaged\")\n        score += max(0, (100 - d.get(\"score\", 100)) // 4)\n\n    m = results.get(\"mail_tracer\") or {}\n    if m.get(\"trust_level\"):\n        findings.append(f\"Email trust: {m.get('trust_level')} ({m.get('trust_score')}/100)\")\n        if m.get(\"flags\", {}).get(\"disposable\"):\n            score += 15\n        if not m.get(\"flags\", {}).get(\"deliverable\"):\n            score += 10\n\n    h = results.get(\"holehe\") or {}\n    if isinstance(h, dict) and h.get(\"found_on\"):\n        findings.append(f\"Email registered on {len(h.get('found_on'))} platforms\")\n        score += min(15, len(h.get(\"found_on\")) * 2)\n\n    c = results.get(\"code_hunter\") or {}\n    if c.get(\"login\") and c.get(\"public_repos\"):\n        findings.append(f\"GitHub: {c.get('public_repos')} repos · {c.get('total_stars', 0)} stars · {len(c.get('commit_emails', []))} commit emails harvested\")\n        if c.get(\"commit_emails\"):\n            score += 10\n\n    w = results.get(\"wayback\") or {}\n    if w.get(\"total_snapshots\"):\n        findings.append(f\"Wayback: {w.get('total_snapshots'):,} snapshots · {w.get('sensitive_count', 0)} sensitive paths flagged\")\n        score += min(20, w.get(\"sensitive_count\", 0))\n\n    pst = results.get(\"paste\") or {}\n    if pst.get(\"total_hits\"):\n        critical = sum(1 for r in pst.get(\"results\", []) if r.get(\"severity\") == \"CRITICAL\")\n        findings.append(f\"Pastes: {pst.get('total_hits')} hits ({critical} flagged critical)\")\n        score += min(25, critical * 5)\n\n    sig = results.get(\"sigint\") or {}\n    if sig.get(\"total\"):\n        findings.append(f\"Sigint: {sig.get('total')} mentions across feeds\")\n\n    score = max(0, min(100, score))\n    verdict = (\n        \"CRITICAL\" if score >= 75 else\n        \"HIGH\" if score >= 50 else\n        \"MODERATE\" if score >= 25 else\n        \"LOW\"\n    )\n\n    if not findings:\n        findings.append(\"No notable signals detected across configured modules.\")\n\n    return {\n        \"target\": target,\n        \"detected_type\": detected,\n        \"modules_run\": list(results.keys()),\n        \"risk_score\": score,\n        \"verdict\": verdict,\n        \"findings\": findings,\n        \"raw\": results,\n    }\n"
  },
  {
    "path": "the_big_brother/modules/breach_vault.py",
    "content": "\"\"\"\nBREACH VAULT — Data breach checker.\nUses HaveIBeenPwned v3 k-anonymity API (no API key needed for passwords,\nbreaches endpoint uses public API). Also checks paste aggregators.\n\"\"\"\n\nimport asyncio\nimport aiohttp\nimport hashlib\nimport requests\nfrom typing import Optional\n\nHIBP_BREACH_URL = \"https://haveibeenpwned.com/api/v3/breachedaccount/{}\"\nHIBP_PASTE_URL  = \"https://haveibeenpwned.com/api/v3/pasteaccount/{}\"\nHIBP_PWNED_URL  = \"https://api.pwnedpasswords.com/range/{}\"\n\nHEADERS = {\n    \"User-Agent\": \"TheBigBrotherV4-OSINT\",\n    \"hibp-api-key\": \"\",  # Optional: set via env var HIBP_API_KEY for full access\n}\n\n# Paste sites for domain leak scraping\nPASTE_SEARCH_URLS = [\n    \"https://psbdmp.ws/api/v3/search/{}\",  # Pastebin dump search (public)\n]\n\nSEVERITY_MAP = {\n    \"Passwords\": \"CRITICAL\",\n    \"Email addresses\": \"HIGH\",\n    \"Phone numbers\": \"HIGH\",\n    \"Physical addresses\": \"HIGH\",\n    \"Credit cards\": \"CRITICAL\",\n    \"Financial data\": \"CRITICAL\",\n    \"Government issued IDs\": \"CRITICAL\",\n    \"Usernames\": \"MEDIUM\",\n    \"Dates of birth\": \"MEDIUM\",\n    \"Social media profiles\": \"MEDIUM\",\n    \"Security questions and answers\": \"HIGH\",\n    \"IP addresses\": \"LOW\",\n    \"Geographic locations\": \"LOW\",\n    \"Browser user agent details\": \"LOW\",\n    \"Website activity\": \"LOW\",\n}\n\n\ndef get_severity(data_classes: list) -> str:\n    \"\"\"Determine worst severity from breach data classes.\"\"\"\n    order = [\"CRITICAL\", \"HIGH\", \"MEDIUM\", \"LOW\", \"INFO\"]\n    worst = \"LOW\"\n    for dc in data_classes:\n        sev = SEVERITY_MAP.get(dc, \"LOW\")\n        if order.index(sev) < order.index(worst):\n            worst = sev\n    return worst\n\n\nasync def check_breaches_hibp(email: str) -> list:\n    \"\"\"Check for breaches using HIBP v3 API.\"\"\"\n    import os\n    api_key = os.environ.get(\"HIBP_API_KEY\", \"\")\n    \n    headers = {\n        \"User-Agent\": \"TheBigBrotherV4-OSINT\",\n        \"hibp-api-key\": api_key,\n    }\n\n    url = f\"https://haveibeenpwned.com/api/v3/breachedaccount/{email}?truncateResponse=false\"\n    \n    try:\n        async with aiohttp.ClientSession() as session:\n            async with session.get(url, headers=headers, timeout=aiohttp.ClientTimeout(total=10), ssl=False) as resp:\n                if resp.status == 404:\n                    return []\n                if resp.status == 401:\n                    return [{\"_note\": \"HIBP API key required for breach lookup. Set HIBP_API_KEY env var.\"}]\n                if resp.status == 200:\n                    data = await resp.json()\n                    breaches = []\n                    for b in data:\n                        breaches.append({\n                            \"name\": b.get(\"Name\"),\n                            \"domain\": b.get(\"Domain\"),\n                            \"date\": b.get(\"BreachDate\"),\n                            \"pwn_count\": b.get(\"PwnCount\"),\n                            \"description\": b.get(\"Description\", \"\")[:300],\n                            \"data_classes\": b.get(\"DataClasses\", []),\n                            \"severity\": get_severity(b.get(\"DataClasses\", [])),\n                            \"is_sensitive\": b.get(\"IsSensitive\", False),\n                            \"is_verified\": b.get(\"IsVerified\", True),\n                            \"logo\": f\"https://haveibeenpwned.com/Content/Images/PwnedLogos/{b.get('Name')}.png\",\n                        })\n                    return sorted(breaches, key=lambda x: x[\"date\"], reverse=True)\n    except Exception as e:\n        print(f\"HIBP breach error: {e}\")\n    return []\n\n\nasync def check_pastes_hibp(email: str) -> list:\n    \"\"\"Check for paste exposures via HIBP.\"\"\"\n    import os\n    api_key = os.environ.get(\"HIBP_API_KEY\", \"\")\n    if not api_key:\n        return []\n\n    headers = {\n        \"User-Agent\": \"TheBigBrotherV4-OSINT\",\n        \"hibp-api-key\": api_key,\n    }\n    url = f\"https://haveibeenpwned.com/api/v3/pasteaccount/{email}\"\n\n    try:\n        async with aiohttp.ClientSession() as session:\n            async with session.get(url, headers=headers, timeout=aiohttp.ClientTimeout(total=10), ssl=False) as resp:\n                if resp.status == 200:\n                    data = await resp.json()\n                    return [{\"source\": p.get(\"Source\"), \"id\": p.get(\"Id\"), \"date\": p.get(\"Date\"), \"emails\": p.get(\"EmailCount\")} for p in data]\n    except Exception as e:\n        print(f\"HIBP paste error: {e}\")\n    return []\n\n\nasync def check_password_pwned(password: str) -> dict:\n    \"\"\"\n    Check if a password has been seen in breaches using k-anonymity model.\n    Never sends the full password — only first 5 chars of SHA1 hash.\n    \"\"\"\n    sha1 = hashlib.sha1(password.encode(\"utf-8\")).hexdigest().upper()\n    prefix = sha1[:5]\n    suffix = sha1[5:]\n\n    url = f\"https://api.pwnedpasswords.com/range/{prefix}\"\n    try:\n        async with aiohttp.ClientSession() as session:\n            async with session.get(url, timeout=aiohttp.ClientTimeout(total=8), ssl=False) as resp:\n                if resp.status == 200:\n                    text = await resp.text()\n                    for line in text.splitlines():\n                        parts = line.split(\":\")\n                        if len(parts) == 2 and parts[0] == suffix:\n                            return {\"pwned\": True, \"count\": int(parts[1])}\n    except Exception as e:\n        print(f\"HIBP password check error: {e}\")\n    return {\"pwned\": False, \"count\": 0}\n\n\nasync def check_paste_aggregator(query: str) -> list:\n    \"\"\"Checks public paste dump APIs for mentions of query (email/domain).\"\"\"\n    results = []\n    url = f\"https://psbdmp.ws/api/v3/search/{query}\"\n    try:\n        async with aiohttp.ClientSession() as session:\n            async with session.get(url, timeout=aiohttp.ClientTimeout(total=8), ssl=False) as resp:\n                if resp.status == 200:\n                    data = await resp.json()\n                    if isinstance(data, dict) and \"data\" in data:\n                        for item in data[\"data\"][:10]:\n                            results.append({\n                                \"source\": \"Pastebin\",\n                                \"id\": item.get(\"id\"),\n                                \"date\": item.get(\"time\"),\n                                \"snippet\": item.get(\"tags\", \"\"),\n                                \"url\": f\"https://pastebin.com/{item.get('id')}\",\n                            })\n    except Exception as e:\n        print(f\"Paste aggregator error: {e}\")\n    return results\n\n\nasync def breach_vault_search(query: str, query_type: str = \"email\"):\n    \"\"\"\n    Main entry point for BREACH VAULT.\n    query_type: 'email' | 'password'\n    \"\"\"\n    if query_type == \"password\":\n        result = await check_password_pwned(query)\n        return {\n            \"query\": \"***REDACTED***\",\n            \"type\": \"password\",\n            \"pwned\": result[\"pwned\"],\n            \"count\": result[\"count\"],\n            \"breaches\": [],\n            \"pastes\": [],\n        }\n\n    # Email query\n    breaches, pastes, paste_dumps = await asyncio.gather(\n        check_breaches_hibp(query),\n        check_pastes_hibp(query),\n        check_paste_aggregator(query),\n    )\n\n    total_exposed = sum(b.get(\"pwn_count\", 0) for b in breaches if isinstance(b.get(\"pwn_count\"), int))\n\n    return {\n        \"query\": query,\n        \"type\": \"email\",\n        \"breach_count\": len(breaches),\n        \"paste_count\": len(pastes),\n        \"total_records_exposed\": total_exposed,\n        \"breaches\": breaches,\n        \"pastes\": pastes,\n        \"paste_dumps\": paste_dumps,\n    }\n"
  },
  {
    "path": "the_big_brother/modules/code_hunter.py",
    "content": "\"\"\"\nCODE HUNTER — GitHub OSINT via public API (no auth required, rate-limited to 60/hr/IP).\nPulls user profile, repos, top languages, public commit emails, organizations.\n\"\"\"\nfrom __future__ import annotations\n\nimport asyncio\nfrom collections import Counter\nfrom datetime import datetime\nfrom typing import Optional\n\nimport requests\n\nAPI = \"https://api.github.com\"\nHEADERS = {\"Accept\": \"application/vnd.github+json\", \"User-Agent\": \"TheBigBrother-V5\"}\n\n\ndef _get(path: str, params: Optional[dict] = None):\n    try:\n        r = requests.get(f\"{API}{path}\", headers=HEADERS, params=params or {}, timeout=8)\n        if r.status_code == 403:\n            return None, \"Rate-limited (60/hr unauthenticated). Add a GITHUB_TOKEN env var to lift the cap.\"\n        if r.status_code == 404:\n            return None, \"Not found\"\n        if r.status_code != 200:\n            return None, f\"HTTP {r.status_code}\"\n        return r.json(), None\n    except Exception as e:\n        return None, str(e)\n\n\ndef _commit_emails(login: str, repos: list) -> list:\n    emails = Counter()\n    for repo in repos[:5]:  # Sample top 5 most-recent\n        data, _ = _get(f\"/repos/{login}/{repo['name']}/commits\", {\"author\": login, \"per_page\": 30})\n        if not data:\n            continue\n        for c in data:\n            author = (c.get(\"commit\") or {}).get(\"author\") or {}\n            mail = author.get(\"email\")\n            if mail and \"noreply\" not in mail and \"users.noreply.github.com\" not in mail:\n                emails[mail] += 1\n    return [{\"email\": e, \"count\": c} for e, c in emails.most_common(10)]\n\n\ndef _activity_buckets(events: list) -> dict:\n    by_hour = [0] * 24\n    by_dow = [0] * 7\n    by_type = Counter()\n    for e in events or []:\n        try:\n            dt = datetime.fromisoformat(e[\"created_at\"].replace(\"Z\", \"+00:00\"))\n            by_hour[dt.hour] += 1\n            by_dow[dt.weekday()] += 1\n            by_type[e[\"type\"]] += 1\n        except Exception:\n            pass\n    return {\n        \"hourly_utc\": by_hour,\n        \"weekday\": by_dow,\n        \"event_types\": dict(by_type.most_common(10)),\n    }\n\n\nasync def code_hunter(login: str) -> dict:\n    login = login.strip().lstrip(\"@\")\n    if not login:\n        return {\"error\": \"username required\"}\n\n    user, err = await asyncio.to_thread(_get, f\"/users/{login}\")\n    if err:\n        return {\"error\": err}\n\n    repos_task = asyncio.to_thread(_get, f\"/users/{login}/repos\", {\"sort\": \"updated\", \"per_page\": 100})\n    orgs_task = asyncio.to_thread(_get, f\"/users/{login}/orgs\")\n    events_task = asyncio.to_thread(_get, f\"/users/{login}/events/public\", {\"per_page\": 100})\n    gists_task = asyncio.to_thread(_get, f\"/users/{login}/gists\", {\"per_page\": 30})\n\n    (repos, _), (orgs, _), (events, _), (gists, _) = await asyncio.gather(\n        repos_task, orgs_task, events_task, gists_task\n    )\n\n    repos = repos or []\n    orgs = orgs or []\n    events = events or []\n    gists = gists or []\n\n    repo_summary = sorted(\n        [\n            {\n                \"name\": r[\"name\"],\n                \"stars\": r.get(\"stargazers_count\", 0),\n                \"forks\": r.get(\"forks_count\", 0),\n                \"language\": r.get(\"language\"),\n                \"updated\": r.get(\"updated_at\"),\n                \"description\": (r.get(\"description\") or \"\")[:140],\n                \"url\": r.get(\"html_url\"),\n                \"fork\": r.get(\"fork\", False),\n            }\n            for r in repos\n        ],\n        key=lambda x: x[\"stars\"],\n        reverse=True,\n    )\n\n    lang_count = Counter(r[\"language\"] for r in repo_summary if r[\"language\"])\n    languages = [{\"name\": k, \"count\": v} for k, v in lang_count.most_common(10)]\n\n    emails = await asyncio.to_thread(_commit_emails, login, repo_summary[:5])\n    activity = _activity_buckets(events)\n\n    total_stars = sum(r[\"stars\"] for r in repo_summary)\n    score = min(100, user.get(\"followers\", 0) // 2 + total_stars // 5 + len(repo_summary) // 2)\n\n    return {\n        \"login\": login,\n        \"name\": user.get(\"name\"),\n        \"avatar\": user.get(\"avatar_url\"),\n        \"bio\": user.get(\"bio\"),\n        \"company\": user.get(\"company\"),\n        \"location\": user.get(\"location\"),\n        \"blog\": user.get(\"blog\"),\n        \"twitter\": user.get(\"twitter_username\"),\n        \"email_public\": user.get(\"email\"),\n        \"created_at\": user.get(\"created_at\"),\n        \"updated_at\": user.get(\"updated_at\"),\n        \"public_repos\": user.get(\"public_repos\"),\n        \"public_gists\": user.get(\"public_gists\"),\n        \"followers\": user.get(\"followers\"),\n        \"following\": user.get(\"following\"),\n        \"html_url\": user.get(\"html_url\"),\n        \"influence_score\": score,\n        \"total_stars\": total_stars,\n        \"top_repos\": repo_summary[:12],\n        \"languages\": languages,\n        \"orgs\": [{\"login\": o[\"login\"], \"url\": o.get(\"url\")} for o in orgs[:20]],\n        \"commit_emails\": emails,\n        \"gist_count\": len(gists),\n        \"activity\": activity,\n    }\n"
  },
  {
    "path": "the_big_brother/modules/crypto_analyzer.py",
    "content": "import requests\nimport datetime\n\ndef analyze_crypto(address: str, coin: str):\n    \"\"\"\n    Analyzes a crypto address for balance and activity.\n    Supports BTC, ETH, LTC, and TRX via free public APIs.\n    \"\"\"\n    results = {\n        \"coin\": coin.upper(),\n        \"address\": address,\n        \"balance\": 0,\n        \"total_received\": 0,\n        \"tx_count\": 0,\n        \"last_seen\": \"Never\",\n        \"recent_txs\": [],\n        \"error\": None\n    }\n    \n    try:\n        if coin.lower() == \"btc\":\n            url = f\"https://blockchain.info/rawaddr/{address}\"\n            resp = requests.get(url, timeout=10)\n            if resp.status_code == 200:\n                data = resp.json()\n                results[\"balance\"] = data.get(\"final_balance\", 0) / 100000000\n                results[\"total_received\"] = data.get(\"total_received\", 0) / 100000000\n                results[\"tx_count\"] = data.get(\"n_tx\", 0)\n                \n                txs = data.get(\"txs\", [])\n                if txs:\n                    last_time = txs[0].get(\"time\")\n                    if last_time:\n                        results[\"last_seen\"] = datetime.datetime.fromtimestamp(last_time).strftime('%Y-%m-%d %H:%M:%S')\n                    for t in txs[:5]:\n                        results[\"recent_txs\"].append({\n                            \"hash\": t.get(\"hash\"),\n                            \"time\": datetime.datetime.fromtimestamp(t.get(\"time\")).strftime('%Y-%m-%d %H:%M') if t.get(\"time\") else \"Unknown\",\n                            \"result\": t.get(\"result\", 0) / 100000000\n                        })\n            else:\n                 results[\"error\"] = f\"API returned {resp.status_code}\"\n                 \n        elif coin.lower() == \"eth\":\n            url = f\"https://api.blockcypher.com/v1/eth/main/addrs/{address}\"\n            resp = requests.get(url, timeout=10)\n            if resp.status_code == 200:\n                data = resp.json()\n                results[\"balance\"] = data.get(\"balance\", 0) / 10**18\n                results[\"total_received\"] = data.get(\"total_received\", 0) / 10**18\n                results[\"tx_count\"] = data.get(\"n_tx\", 0)\n                \n                txrefs = data.get(\"txrefs\", [])\n                if txrefs:\n                    results[\"last_seen\"] = txrefs[0].get(\"confirmed\", \"Check Explorer\")[:19].replace(\"T\", \" \")\n                    for t in txrefs[:5]:\n                        results[\"recent_txs\"].append({\n                            \"hash\": t.get(\"tx_hash\"),\n                            \"time\": t.get(\"confirmed\", \"\")[:16].replace(\"T\", \" \"),\n                            \"result\": t.get(\"value\", 0) / 10**18\n                        })\n                else:\n                    results[\"last_seen\"] = \"Check Explorer\"\n            else:\n                 results[\"error\"] = f\"API returned {resp.status_code}\"\n\n        elif coin.lower() == \"ltc\":\n            url = f\"https://api.blockcypher.com/v1/ltc/main/addrs/{address}\"\n            resp = requests.get(url, timeout=10)\n            if resp.status_code == 200:\n                data = resp.json()\n                results[\"balance\"] = data.get(\"balance\", 0) / 10**8\n                results[\"total_received\"] = data.get(\"total_received\", 0) / 10**8\n                results[\"tx_count\"] = data.get(\"n_tx\", 0)\n                \n                txrefs = data.get(\"txrefs\", [])\n                if txrefs:\n                    results[\"last_seen\"] = txrefs[0].get(\"confirmed\", \"Check Explorer\")[:19].replace(\"T\", \" \")\n                    for t in txrefs[:5]:\n                        results[\"recent_txs\"].append({\n                            \"hash\": t.get(\"tx_hash\"),\n                            \"time\": t.get(\"confirmed\", \"\")[:16].replace(\"T\", \" \"),\n                            \"result\": t.get(\"value\", 0) / 10**8\n                        })\n                else:\n                    results[\"last_seen\"] = \"Check Explorer\"\n            else:\n                 results[\"error\"] = f\"API returned {resp.status_code}\"\n\n        elif coin.lower() == \"trx\":\n            url = f\"https://apilist.tronscanapi.com/api/accountv2?address={address}\"\n            resp = requests.get(url, timeout=10)\n            if resp.status_code == 200:\n                data = resp.json()\n                results[\"balance\"] = data.get(\"balance\", 0) / 1000000\n                results[\"total_received\"] = data.get(\"totalTransactionCount\", 0) # Not exactly total received but useful\n                results[\"tx_count\"] = data.get(\"transactions\", 0)\n                \n                last = data.get(\"latest_operation_time\")\n                if last:\n                    results[\"last_seen\"] = datetime.datetime.fromtimestamp(last/1000).strftime('%Y-%m-%d %H:%M:%S')\n            else:\n                 results[\"error\"] = f\"API returned {resp.status_code}\"\n    \n    except Exception as e:\n        results[\"error\"] = str(e)\n        \n    return results\n"
  },
  {
    "path": "the_big_brother/modules/dark_watch.py",
    "content": "import requests\nfrom bs4 import BeautifulSoup\nimport asyncio\n\nasync def search_ransomware_leaks(query: str):\n    \"\"\"\n    Searches known ransomware leak sites via Ransomwatch feed.\n    \"\"\"\n    url = \"https://raw.githubusercontent.com/joshhighet/ransomwatch/main/posts.json\"\n    try:\n        resp = await asyncio.to_thread(requests.get, url, timeout=10)\n        data = resp.json()\n        \n        matches = []\n        query_lower = query.lower()\n        \n        for post in data:\n            # post fields: group_name, post_title, discovered, etc.\n            title = post.get(\"post_title\", \"\").lower()\n            group = post.get(\"group_name\", \"\").lower()\n            \n            if query_lower in title or query_lower in group:\n                matches.append({\n                    \"title\": f\"[{post.get('group_name')}] {post.get('post_title')}\",\n                    \"link\": \"#\", # Usually no direct link in this JSON without digging, but group name is key\n                    \"snippet\": f\"Ransomware Leak Discovered: {post.get('discovered')}\",\n                    \"date\": post.get(\"discovered\")\n                })\n        return matches\n    except Exception as e:\n        print(f\"Ransomwatch error: {e}\")\n        return []\n\nasync def search_dark_web(query: str):\n    \"\"\"\n    Searches Ahmia.fi for onion links AND checks ransomware leaks.\n    \"\"\"\n    results = []\n    \n    # 1. Ransomware Search\n    ransom_results = await search_ransomware_leaks(query)\n    results.extend(ransom_results)\n    \n    # 2. Onion Search (Ahmia)\n    url = f\"https://ahmia.fi/search/?q={query}\"\n    \n    headers = {\n        \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0\"\n    }\n    \n    try:\n        resp = await asyncio.to_thread(requests.get, url, headers=headers, timeout=10)\n        \n        if resp.status_code == 200:\n            soup = BeautifulSoup(resp.text, 'html.parser')\n            \n            for li in soup.find_all('li', class_='result'):\n                try:\n                    link = li.find('a')['href']\n                    title = li.find('a').text.strip()\n                    snippet = li.find('p').text.strip() if li.find('p') else \"No description\"\n                    \n                    date = li.find('span', class_='modified')\n                    date_str = date.text.strip() if date else \"Unknown Date\"\n                    \n                    results.append({\n                        \"title\": title,\n                        \"link\": link,\n                        \"snippet\": snippet,\n                        \"date\": date_str\n                    })\n                except:\n                    continue\n                    \n        return {\"results\": results, \"count\": len(results)}\n        \n    except Exception as e:\n        # Return what we have if Ahmia fails\n        if results:\n             return {\"results\": results, \"count\": len(results)}\n        return {\"error\": str(e)}\n\nasync def check_tor_status(onion_url: str):\n    \"\"\"\n    Checks if an onion link is live using a tor2web proxy if local Tor isn't available,\n    or just returns a specific message.\n    Real Tor checking requires a SOCKS proxy (like 127.0.0.1:9050).\n    We will assume Tor might not be configured in this Docker container yet, \n    so we check via a public gateway like onion.ly for basic status.\n    \"\"\"\n    # Convert .onion to .onion.ly for public check\n    gateway_url = onion_url.replace(\".onion\", \".onion.ly\")\n    if not gateway_url.startswith(\"http\"):\n        gateway_url = \"http://\" + gateway_url\n        \n    try:\n        resp = await asyncio.to_thread(requests.head, gateway_url, timeout=5)\n        return {\"status\": \"Online\" if resp.status_code == 200 else \"Offline\", \"code\": resp.status_code}\n    except:\n        return {\"status\": \"Unreachable\", \"code\": 0}\n"
  },
  {
    "path": "the_big_brother/modules/digital_footprint.py",
    "content": "import phonenumbers\nfrom phonenumbers import geocoder, carrier, timezone\n\nimport asyncio\nimport subprocess\nimport json\nimport dns.resolver\n\nasync def check_email_osint(email: str):\n    \"\"\"\n    Checks email against holehe's list of sites.\n    \"\"\"\n    out = []\n    \n    # Holehe allows checking specific modules or all.\n    # For speed in this demo, we might want to limit or just run the standard set.\n    # Using the importable check_email function from holehe\n    \n    # Note: holehe is primarily CLI based but has core functions.\n    # We will wrap it. \n    \n    # Since holehe might be slow, it's best run in a background task or thread.\n    # Here is a simplified synchronous wrapper that we'll call asynchronously.\n    \n    from holehe.core import import_submodules\n    modules = import_submodules(\"holehe.modules\")\n    \n    results = []\n    \n    for module in modules:\n        try:\n            # Each module has a [module_name] class or function\n            # This is a simplified integration based on holehe structure\n             if hasattr(module, str(module.__name__).split(\".\")[-1]):\n                check_func = getattr(module, str(module.__name__).split(\".\")[-1])\n                # most holehe modules take email, client, out\n                # We need to inspect holehe source for exact internal API or use CLI wrapper\n                # For safety and stability, maybe shelling out is safer if internal API is unstable\n                pass\n        except Exception:\n            pass\n\n    # Alternative: Shell out to holehe CLI for stability if library use is complex\n    # But let's try a direct approach if possible or fallback to a simulated \"quick check\" \n    # using known patterns if holehe is too heavy.\n    \n    # REVISION: To ensure this works without deep diving into holehe's internal non-public API,\n    # let's use a subprocess to call 'holehe' if it's installed as a binary, \n    # OR better, since we installed it via pip, we can try to use its published entry points.\n    \n    # Let's implement a robust phone checker first as it is pure library call.\n    return {\"status\": \"scan_started\", \"email\": email}\n\ndef get_phone_info(number_str: str):\n    try:\n        parsed_number = phonenumbers.parse(number_str, None)\n        if not phonenumbers.is_valid_number(parsed_number):\n            return {\"error\": \"Invalid number\"}\n            \n        # Get line type\n        line_type_code = phonenumbers.number_type(parsed_number)\n        line_type_map = {0: \"FIXED_LINE\", 1: \"MOBILE\", 2: \"FIXED_OR_MOBILE\", 3: \"TOLL_FREE\", 4: \"PREMIUM_RATE\", \n                         5: \"SHARED_COST\", 6: \"VOIP\", 7: \"PERSONAL_NUMBER\", 8: \"PAGER\", 9: \"UAN\", 10: \"VOICEMAIL\"}\n        line_type = line_type_map.get(line_type_code, \"UNKNOWN\")\n\n        # Get Timezones\n        tzs = timezone.time_zones_for_number(parsed_number)\n        \n        return {\n            \"valid\": True,\n            \"number\": phonenumbers.format_number(parsed_number, phonenumbers.PhoneNumberFormat.INTERNATIONAL),\n            \"country\": geocoder.description_for_number(parsed_number, \"en\"),\n            \"carrier\": carrier.name_for_number(parsed_number, \"en\"),\n            \"line_type\": line_type,\n            \"timezones\": list(tzs)\n        }\n    except Exception as e:\n        return {\"error\": str(e)}\n\n# Re-implementing email check to be robust\nimport subprocess\nimport json\n\nasync def run_holehe(email: str):\n    \"\"\"\n    Runs holehe as a subprocess. Also checks MX records.\n    \"\"\"\n    results = {\"email\": email, \"found_on\": [], \"mx_records\": [], \"valid_mx\": False}\n\n    # 1. MX Record Check\n    try:\n        domain = email.split('@')[-1]\n        mx_records = dns.resolver.resolve(domain, 'MX')\n        for mx in mx_records:\n            results[\"mx_records\"].append(str(mx.exchange))\n        if results[\"mx_records\"]:\n            results[\"valid_mx\"] = True\n    except:\n        pass\n\n    # 2. Holehe Scan\n    cmd = [\"holehe\", email, \"--only-used\", \"--no-color\"]\n    try:\n        proc = await asyncio.create_subprocess_exec(\n            *cmd,\n            stdout=asyncio.subprocess.PIPE,\n            stderr=asyncio.subprocess.PIPE\n        )\n        \n        stdout, stderr = await proc.communicate()\n        output = stdout.decode()\n        \n        sites = []\n        for line in output.splitlines():\n            if \"[+]\" in line:\n                parts = line.split(\" \")\n                site = parts[-1]\n                if site not in sites:\n                    sites.append(site)\n        results[\"found_on\"] = sites\n    except Exception as e:\n        print(f\"Holehe error: {e}\")\n        \n    return results\n"
  },
  {
    "path": "the_big_brother/modules/domain_oracle.py",
    "content": "\"\"\"\nDOMAIN ORACLE — Passive deep domain intelligence.\nPulls WHOIS (RDAP), full DNS set, SPF/DMARC/DKIM posture, HTTP security headers,\nTLS basic info, and subdomain enumeration via crt.sh — no auth keys required.\n\"\"\"\nimport asyncio\nimport socket\nimport ssl\nimport re\nfrom urllib.parse import urlparse\n\nimport requests\nimport dns.resolver\n\n\nSECURITY_HEADERS = [\n    \"Strict-Transport-Security\",\n    \"Content-Security-Policy\",\n    \"X-Frame-Options\",\n    \"X-Content-Type-Options\",\n    \"Referrer-Policy\",\n    \"Permissions-Policy\",\n]\n\n\ndef _resolver():\n    r = dns.resolver.Resolver()\n    r.timeout = 2\n    r.lifetime = 3\n    return r\n\n\ndef _query(domain: str, rtype: str):\n    out = []\n    try:\n        for rec in _resolver().resolve(domain, rtype):\n            out.append(str(rec).strip('\"'))\n    except Exception:\n        pass\n    return out\n\n\ndef _rdap(domain: str) -> dict:\n    try:\n        r = requests.get(f\"https://rdap.org/domain/{domain}\", timeout=6)\n        if r.status_code != 200:\n            return {}\n        data = r.json()\n        events = {e.get(\"eventAction\"): e.get(\"eventDate\") for e in data.get(\"events\", [])}\n        registrar = \"Unknown\"\n        for ent in data.get(\"entities\", []):\n            if \"registrar\" in ent.get(\"roles\", []):\n                v = ent.get(\"vcardArray\")\n                if v and len(v) > 1:\n                    for item in v[1]:\n                        if item[0] == \"fn\":\n                            registrar = item[3]\n                            break\n        ns = []\n        for n in data.get(\"nameservers\", []):\n            ld = n.get(\"ldhName\")\n            if ld:\n                ns.append(ld.lower())\n        return {\n            \"registrar\": registrar,\n            \"status\": data.get(\"status\", []),\n            \"created\": events.get(\"registration\", \"Unknown\"),\n            \"updated\": events.get(\"last changed\", events.get(\"last update of RDAP database\", \"Unknown\")),\n            \"expires\": events.get(\"expiration\", \"Unknown\"),\n            \"nameservers\": ns,\n        }\n    except Exception:\n        return {}\n\n\ndef _spf(txt_records: list) -> dict:\n    spf = next((t for t in txt_records if t.lower().startswith(\"v=spf1\")), None)\n    if not spf:\n        return {\"present\": False, \"policy\": None, \"grade\": \"F\"}\n    policy = \"neutral\"\n    if \" -all\" in spf:\n        policy = \"strict (-all)\"\n        grade = \"A\"\n    elif \" ~all\" in spf:\n        policy = \"softfail (~all)\"\n        grade = \"B\"\n    elif \" ?all\" in spf:\n        policy = \"neutral (?all)\"\n        grade = \"C\"\n    elif \" +all\" in spf:\n        policy = \"pass-all (+all) — DANGEROUS\"\n        grade = \"F\"\n    else:\n        grade = \"C\"\n    return {\"present\": True, \"record\": spf, \"policy\": policy, \"grade\": grade}\n\n\ndef _dmarc(domain: str) -> dict:\n    recs = _query(f\"_dmarc.{domain}\", \"TXT\")\n    rec = next((r for r in recs if r.lower().startswith(\"v=dmarc1\")), None)\n    if not rec:\n        return {\"present\": False, \"grade\": \"F\"}\n    policy = \"none\"\n    if \"p=reject\" in rec.lower():\n        policy, grade = \"reject\", \"A\"\n    elif \"p=quarantine\" in rec.lower():\n        policy, grade = \"quarantine\", \"B\"\n    else:\n        policy, grade = \"none\", \"D\"\n    return {\"present\": True, \"record\": rec, \"policy\": policy, \"grade\": grade}\n\n\ndef _dkim(domain: str) -> dict:\n    selectors = [\"default\", \"google\", \"selector1\", \"selector2\", \"k1\", \"mail\", \"dkim\", \"smtp\"]\n    found = {}\n    for s in selectors:\n        recs = _query(f\"{s}._domainkey.{domain}\", \"TXT\")\n        rec = next((r for r in recs if \"v=DKIM\" in r or \"k=\" in r), None)\n        if rec:\n            found[s] = rec[:200]\n    return {\n        \"present\": len(found) > 0,\n        \"selectors_found\": list(found.keys()),\n        \"samples\": found,\n        \"grade\": \"A\" if found else \"F\",\n    }\n\n\ndef _http_security_headers(domain: str) -> dict:\n    out = {\"https_reachable\": False, \"headers\": {}, \"missing\": [], \"grade\": \"F\"}\n    for scheme in (\"https\", \"http\"):\n        try:\n            r = requests.get(f\"{scheme}://{domain}\", timeout=6, allow_redirects=True)\n            present = {}\n            for h in SECURITY_HEADERS:\n                if h in r.headers:\n                    present[h] = r.headers[h][:200]\n            out[\"https_reachable\"] = scheme == \"https\"\n            out[\"status\"] = r.status_code\n            out[\"server\"] = r.headers.get(\"Server\", \"—\")\n            out[\"powered_by\"] = r.headers.get(\"X-Powered-By\", \"—\")\n            out[\"headers\"] = present\n            out[\"missing\"] = [h for h in SECURITY_HEADERS if h not in present]\n            score = len(present) / len(SECURITY_HEADERS)\n            out[\"grade\"] = (\n                \"A\" if score >= 0.83 else\n                \"B\" if score >= 0.66 else\n                \"C\" if score >= 0.5 else\n                \"D\" if score >= 0.33 else \"F\"\n            )\n            return out\n        except Exception:\n            continue\n    return out\n\n\ndef _tls_meta(domain: str) -> dict:\n    try:\n        ctx = ssl.create_default_context()\n        with socket.create_connection((domain, 443), timeout=5) as sock:\n            with ctx.wrap_socket(sock, server_hostname=domain) as ssock:\n                cert = ssock.getpeercert()\n                return {\n                    \"tls_version\": ssock.version(),\n                    \"cipher\": ssock.cipher()[0] if ssock.cipher() else None,\n                    \"issuer\": dict(x[0] for x in cert.get(\"issuer\", [])),\n                    \"subject\": dict(x[0] for x in cert.get(\"subject\", [])),\n                    \"not_before\": cert.get(\"notBefore\"),\n                    \"not_after\": cert.get(\"notAfter\"),\n                }\n    except Exception as e:\n        return {\"error\": str(e)}\n\n\ndef _subdomains(domain: str) -> list:\n    try:\n        r = requests.get(f\"https://crt.sh/?q=%.{domain}&output=json\", timeout=8)\n        if r.status_code != 200:\n            return []\n        subs = set()\n        for entry in r.json():\n            for n in entry.get(\"name_value\", \"\").split(\"\\n\"):\n                n = n.strip().lower()\n                if n.endswith(domain) and n != domain and \"*\" not in n:\n                    subs.add(n)\n        return sorted(subs)\n    except Exception:\n        return []\n\n\ndef _reverse_ip(ip: str) -> list:\n    try:\n        r = requests.get(f\"https://api.hackertarget.com/reverseiplookup/?q={ip}\", timeout=5)\n        if r.status_code == 200 and \"error\" not in r.text.lower():\n            return [x for x in r.text.splitlines() if x.strip()][:50]\n    except Exception:\n        pass\n    return []\n\n\nasync def domain_oracle(domain: str) -> dict:\n    domain = domain.strip().lower()\n    if domain.startswith((\"http://\", \"https://\")):\n        domain = urlparse(domain).netloc\n    if not re.match(r\"^[a-z0-9.-]+\\.[a-z]{2,}$\", domain):\n        return {\"error\": \"Invalid domain\"}\n\n    try:\n        ip = socket.gethostbyname(domain)\n    except Exception:\n        return {\"error\": \"Could not resolve domain\"}\n\n    dns_records = {}\n    for rtype in (\"A\", \"AAAA\", \"MX\", \"NS\", \"TXT\", \"CAA\", \"SOA\"):\n        dns_records[rtype] = await asyncio.to_thread(_query, domain, rtype)\n\n    rdap = await asyncio.to_thread(_rdap, domain)\n    spf = _spf(dns_records.get(\"TXT\", []))\n    dmarc = await asyncio.to_thread(_dmarc, domain)\n    dkim = await asyncio.to_thread(_dkim, domain)\n    headers = await asyncio.to_thread(_http_security_headers, domain)\n    tls = await asyncio.to_thread(_tls_meta, domain)\n    subs = await asyncio.to_thread(_subdomains, domain)\n    neighbors = await asyncio.to_thread(_reverse_ip, ip)\n\n    grades = [spf[\"grade\"], dmarc[\"grade\"], dkim[\"grade\"], headers[\"grade\"]]\n    score = 100 - (sum({\"A\": 0, \"B\": 10, \"C\": 20, \"D\": 30, \"F\": 40}[g] for g in grades))\n    score = max(0, min(100, score))\n\n    return {\n        \"domain\": domain,\n        \"ip\": ip,\n        \"score\": score,\n        \"rdap\": rdap,\n        \"dns\": dns_records,\n        \"email_security\": {\n            \"spf\": spf,\n            \"dmarc\": dmarc,\n            \"dkim\": dkim,\n        },\n        \"http_security\": headers,\n        \"tls\": tls,\n        \"subdomains\": subs,\n        \"neighbors\": neighbors,\n    }\n"
  },
  {
    "path": "the_big_brother/modules/dork_studio.py",
    "content": "def generate_dorks(target: str, domain: str = \"\"):\n    \"\"\"\n    Generates a list of advanced dorks for various platforms.\n    \"\"\"\n    dorks = {\n        \"google\": [],\n        \"shodan\": [],\n        \"github\": [],\n        \"censys\": [],\n        \"fofa\": []\n    }\n    \n    # ---------------- GOOGLE (AGGRESSIVE) ----------------\n    # SQL Injection\n    dorks[\"google\"].append({\"title\": \"SQL Injection Vectors\", \"query\": f\"site:{domain} inurl:id= | inurl:pid= | inurl:category= | inurl:cat= | inurl:action= | inurl:sid= | inurl:dir= intext:warning intext:mysql\"})\n    # LFI / Directory Traversal\n    dorks[\"google\"].append({\"title\": \"Directory Traversal\", \"query\": f\"site:{domain} inurl:include= | inurl:page= | inurl:file= | inurl:cfg= ext:inc | ext:php\"})\n    # Exposed Git\n    dorks[\"google\"].append({\"title\": \"Exposed .git\", \"query\": f\"site:{domain} intitle:\\\"index of\\\" \\\"/.git\\\"\"})\n    # Public Cameras\n    dorks[\"google\"].append({\"title\": \"Public Cameras\", \"query\": f\"inurl:top.htm inurl:currenttime inurl:pixel\"})\n    # Log Files\n    dorks[\"google\"].append({\"title\": \"Exposed Log Files\", \"query\": f\"site:{domain} ext:log | ext:txt | ext:conf | ext:cnf | ext:ini | ext:env | ext:sh\"})\n    # Install/Setup Pages\n    dorks[\"google\"].append({\"title\": \"Install/Setup Pages\", \"query\": f\"site:{domain} inurl:readme | inurl:license | inurl:install | inurl:setup | inurl:config\"})\n    # S3 Buckets\n    dorks[\"google\"].append({\"title\": \"S3 Buckets\", \"query\": f\"site:s3.amazonaws.com \\\"{target}\\\"\"})\n    # Pastebin Data\n    dorks[\"google\"].append({\"title\": \"Pastebin Leaks\", \"query\": f\"site:pastebin.com \\\"{target}\\\"\"})\n\n    # ---------------- SHODAN (INFRASTRUCTURE) ----------------\n    dorks[\"shodan\"].append({\"title\": \"Organization Infra\", \"query\": f\"org:\\\"{target}\\\"\"})\n    dorks[\"shodan\"].append({\"title\": \"SSL Certificates\", \"query\": f\"ssl:\\\"{target}\\\"\"})\n    dorks[\"shodan\"].append({\"title\": \"Vulnerable SMB\", \"query\": f\"net:\\\"{target}\\\" port:445 has_vuln:true\"})\n    dorks[\"shodan\"].append({\"title\": \"Open Webcams\", \"query\": \"has_screenshot:true port:80,81,8080 title:\\\"webcam\\\"\"})\n    dorks[\"shodan\"].append({\"title\": \"Industrial Control Systems\", \"query\": \"port:502,102,44818 tag:ics\"})\n    dorks[\"shodan\"].append({\"title\": \"Default Passwords\", \"query\": \"\\\"default password\\\" \\\"admin\\\"\"})\n    \n    if domain:\n        dorks[\"shodan\"].append({\"title\": \"Hostname Search\", \"query\": f\"hostname:\\\"{domain}\\\"\"})\n        \n    # ---------------- GITHUB (SECRETS) ----------------\n    dorks[\"github\"].append({\"title\": \"AWS Keys\", \"query\": f\"\\\"{target}\\\" AWS_ACCESS_KEY_ID\"})\n    dorks[\"github\"].append({\"title\": \"API Keys\", \"query\": f\"\\\"{target}\\\" API_KEY OR SECRET_KEY\"})\n    if domain:\n        dorks[\"github\"].append({\"title\": \"Internal Passwords\", \"query\": f\"\\\"{domain}\\\" password OR secret OR key\"})\n\n    # ---------------- CENSYS ----------------\n    dorks[\"censys\"].append({\"title\": \"Search by Domain\", \"query\": f\"services.tls.certificates.leaf_data.names: {domain}\"})\n    dorks[\"censys\"].append({\"title\": \"Search by Org\", \"query\": f\"autonomous_system.name: \\\"{target}\\\"\"})\n\n    # ---------------- FOFA ----------------\n    dorks[\"fofa\"].append({\"title\": \"Domain Search\", \"query\": f\"domain=\\\"{domain}\\\"\"})\n    dorks[\"fofa\"].append({\"title\": \"Title Search\", \"query\": f\"title=\\\"{target}\\\"\"})\n    dorks[\"fofa\"].append({\"title\": \"Cert Search\", \"query\": f\"cert=\\\"{domain}\\\"\"})\n\n    return dorks\n"
  },
  {
    "path": "the_big_brother/modules/exif_analyzer.py",
    "content": "from PIL import Image\nfrom PIL.ExifTags import TAGS, GPSTAGS\nimport requests\nfrom io import BytesIO\n\ndef get_exif_data(image_source: str, is_url: bool = True):\n    \"\"\"\n    Extracts EXIF data from an image URL or local file (simulated via bytes).\n    \"\"\"\n    results = {\n        \"source\": image_source,\n        \"basic\": {},\n        \"gps\": {},\n        \"error\": None\n    }\n    \n    try:\n        image = None\n        if is_url:\n            resp = requests.get(image_source, timeout=10)\n            if resp.status_code == 200:\n                image = Image.open(BytesIO(resp.content))\n            else:\n                 return {\"error\": f\"Failed to download image: {resp.status_code}\"}\n        else:\n             # For now we only support URL in this quick implementation\n             return {\"error\": \"Local file upload not implemented in this version\"}\n\n        if not image:\n             return {\"error\": \"Could not open image\"}\n\n        # Basic Info\n        results[\"basic\"][\"format\"] = image.format\n        results[\"basic\"][\"mode\"] = image.mode\n        results[\"basic\"][\"size\"] = f\"{image.width}x{image.height}\"\n        \n        exif_data = image._getexif()\n        if not exif_data:\n            return results # No exif\n\n        for tag_id, value in exif_data.items():\n            tag = TAGS.get(tag_id, tag_id)\n            \n            # Decode bytes if needed\n            if isinstance(value, bytes):\n                try:\n                    value = value.decode()\n                except:\n                    value = str(value)\n\n            if tag == \"GPSInfo\":\n                gps_data = {}\n                for t in value:\n                    sub_tag = GPSTAGS.get(t, t)\n                    gps_data[sub_tag] = str(value[t])\n                results[\"gps\"] = gps_data\n            else:\n                # Filter out very long binary data (like maker notes)\n                if len(str(value)) < 500:\n                    results[\"basic\"][tag] = value\n\n    except Exception as e:\n        results[\"error\"] = str(e)\n        \n    return results\n"
  },
  {
    "path": "the_big_brother/modules/flight_radar.py",
    "content": "import requests\nimport datetime\n\ndef get_flight_radar(lat: float, lon: float, radius_km: float = 100):\n    \"\"\"\n    Fetches real-time flight data near the target coordinates.\n    Uses OpenSky Network API (Free tier).\n    \"\"\"\n    # 1 degree lat ~ 111km. \n    deg_diff = radius_km / 111.0\n    \n    lamin = lat - deg_diff\n    lamax = lat + deg_diff\n    lomin = lon - deg_diff\n    lomax = lon + deg_diff\n    \n    url = f\"https://opensky-network.org/api/states/all?lamin={lamin}&lomin={lomin}&lamax={lamax}&lomax={lomax}\"\n    \n    results = {\n        \"location\": {\"lat\": lat, \"lon\": lon},\n        \"flights\": [],\n        \"count\": 0,\n        \"error\": None\n    }\n    \n    try:\n        resp = requests.get(url, timeout=10)\n        if resp.status_code == 200:\n            data = resp.json()\n            states = data.get(\"states\", [])\n            \n            if states:\n                for s in states[:20]: # Limit to 20 closest/first\n                    # s indexes: 0=icao24, 1=callsign, 2=origin_country, 5=lon, 6=lat, 13=geo_altitude\n                    results[\"flights\"].append({\n                        \"icao\": s[0],\n                        \"callsign\": s[1].strip(),\n                        \"country\": s[2],\n                        \"lat\": s[6],\n                        \"lon\": s[5],\n                        \"alt\": s[13],\n                        \"velocity\": s[9]\n                    })\n                results[\"count\"] = len(results[\"flights\"])\n            else:\n                 results[\"message\"] = \"No aircraft found in this sector.\"\n        else:\n             results[\"error\"] = f\"Radar Offline: {resp.status_code}\"\n             \n    except Exception as e:\n        results[\"error\"] = str(e)\n        \n    return results\n"
  },
  {
    "path": "the_big_brother/modules/geoint_spy.py",
    "content": "import requests\n\ndef get_geoint_data(lat: str, lon: str):\n    \"\"\"\n    Generates a GEOINT package for the given coordinates.\n    Includes Sat links, SunCalc, and search queries for social media.\n    Reverse geocodes coordinates to a physical address.\n    \"\"\"\n    try:\n        f_lat = float(lat)\n        f_lon = float(lon)\n    except:\n        return {\"error\": \"Invalid coordinates\"}\n\n    # Reverse Geocoding (Nominatim OpenStreetMap)\n    address = \"Unknown Address\"\n    try:\n        headers = {\"User-Agent\": \"TheBigBrotherV4-OSINT\"}\n        resp = requests.get(f\"https://nominatim.openstreetmap.org/reverse?format=json&lat={f_lat}&lon={f_lon}\", headers=headers, timeout=5)\n        if resp.status_code == 200:\n            address = resp.json().get(\"display_name\", \"Address not found\")\n    except:\n        pass\n\n    # Google Maps URL\n    gmaps = f\"https://www.google.com/maps/place/{f_lat},{f_lon}/@{f_lat},{f_lon},18z/data=!3m1!1e3\"\n    \n    # Street View\n    street = f\"https://www.google.com/maps?layer=c&cbll={f_lat},{f_lon}\"\n    \n    # SunCalc (Shadow Analysis)\n    suncalc = f\"https://www.suncalc.org/#/{f_lat},{f_lon},18/now\"\n    \n    # Tweet Locator (Twitter advanced search near location)\n    twitter = f\"https://twitter.com/search?q=geocode%3A{f_lat}%2C{f_lon}%2C1km&src=typed_query&f=live\"\n    \n    # Snapchat Map\n    snapchat = f\"https://map.snapchat.com/@{f_lat},{f_lon},15.00z\"\n    \n    # Wikimapia\n    wikimapia = f\"http://wikimapia.org/#lang=en&lat={f_lat}&lon={f_lon}&z=18&m=b\"\n\n    return {\n        \"coords\": f\"{f_lat}, {f_lon}\",\n        \"address\": address,\n        \"links\": {\n            \"Google Satellite\": gmaps,\n            \"Street View\": street,\n            \"SunCalc (Shadows)\": suncalc,\n            \"Twitter (Nearby)\": twitter,\n            \"Snapchat Map\": snapchat,\n            \"Wikimapia\": wikimapia\n        }\n    }\n"
  },
  {
    "path": "the_big_brother/modules/mail_tracer.py",
    "content": "\"\"\"\nMAIL TRACER — Email infrastructure forensics.\nInspects an email address: MX validity, SPF/DMARC presence, disposable/role flags,\ngravatar lookup, and computes a deliverability/trust score.\n\"\"\"\nimport asyncio\nimport hashlib\nimport re\n\nimport requests\nimport dns.resolver\n\nfrom the_big_brother.modules.domain_oracle import _query, _spf, _dmarc\n\n\nDISPOSABLE = {\n    \"10minutemail.com\", \"guerrillamail.com\", \"mailinator.com\", \"tempmail.com\",\n    \"throwaway.email\", \"trashmail.com\", \"yopmail.com\", \"getairmail.com\",\n    \"fakeinbox.com\", \"sharklasers.com\", \"tempr.email\", \"dispostable.com\",\n    \"maildrop.cc\", \"mintemail.com\", \"mohmal.com\", \"tempinbox.com\",\n}\n\nROLE_LOCALS = {\n    \"admin\", \"administrator\", \"info\", \"support\", \"help\", \"contact\", \"sales\",\n    \"noreply\", \"no-reply\", \"postmaster\", \"webmaster\", \"abuse\", \"security\",\n    \"hr\", \"jobs\", \"careers\", \"marketing\", \"office\", \"team\", \"hello\",\n}\n\nFREE_PROVIDERS = {\n    \"gmail.com\", \"yahoo.com\", \"outlook.com\", \"hotmail.com\", \"icloud.com\",\n    \"protonmail.com\", \"proton.me\", \"tutanota.com\", \"aol.com\", \"yandex.com\",\n    \"mail.com\", \"gmx.com\", \"zoho.com\",\n}\n\n\ndef _gravatar(email: str) -> str:\n    h = hashlib.md5(email.strip().lower().encode()).hexdigest()\n    return f\"https://www.gravatar.com/avatar/{h}?s=256&d=404\"\n\n\ndef _gravatar_exists(url: str) -> bool:\n    try:\n        r = requests.head(url, timeout=5, allow_redirects=False)\n        return r.status_code == 200\n    except Exception:\n        return False\n\n\nasync def mail_tracer(email: str) -> dict:\n    email = email.strip().lower()\n    if not re.match(r\"^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$\", email):\n        return {\"error\": \"Invalid email\"}\n\n    local, domain = email.rsplit(\"@\", 1)\n\n    mx_records = await asyncio.to_thread(_query, domain, \"MX\")\n    txt_records = await asyncio.to_thread(_query, domain, \"TXT\")\n    a_records = await asyncio.to_thread(_query, domain, \"A\")\n\n    spf = _spf(txt_records)\n    dmarc = await asyncio.to_thread(_dmarc, domain)\n\n    is_disposable = domain in DISPOSABLE\n    is_role = local in ROLE_LOCALS\n    is_free = domain in FREE_PROVIDERS\n\n    gravatar_url = _gravatar(email)\n    has_gravatar = await asyncio.to_thread(_gravatar_exists, gravatar_url)\n\n    # Trust score\n    score = 100\n    factors = []\n    if not mx_records:\n        score -= 50\n        factors.append(\"No MX records — domain cannot receive mail\")\n    if not a_records and not mx_records:\n        score -= 20\n        factors.append(\"Domain has no DNS presence\")\n    if is_disposable:\n        score -= 60\n        factors.append(f\"Disposable provider: {domain}\")\n    if is_role:\n        score -= 15\n        factors.append(f\"Role-based local part: {local}\")\n    if not spf[\"present\"]:\n        score -= 10\n        factors.append(\"No SPF record — spoofable\")\n    elif spf[\"grade\"] in (\"C\", \"F\"):\n        score -= 5\n        factors.append(f\"Weak SPF policy: {spf.get('policy')}\")\n    if not dmarc[\"present\"]:\n        score -= 10\n        factors.append(\"No DMARC record — no enforcement\")\n    elif dmarc[\"grade\"] == \"D\":\n        score -= 5\n        factors.append(\"DMARC policy is 'none' (monitor only)\")\n    if has_gravatar:\n        score += 5\n        factors.append(\"Gravatar exists — identity tied\")\n\n    score = max(0, min(100, score))\n\n    trust_level = (\n        \"TRUSTED\" if score >= 80 else\n        \"MODERATE\" if score >= 60 else\n        \"WEAK\" if score >= 40 else\n        \"SUSPICIOUS\"\n    )\n\n    return {\n        \"email\": email,\n        \"local\": local,\n        \"domain\": domain,\n        \"trust_score\": score,\n        \"trust_level\": trust_level,\n        \"factors\": factors,\n        \"flags\": {\n            \"disposable\": is_disposable,\n            \"role_based\": is_role,\n            \"free_provider\": is_free,\n            \"deliverable\": bool(mx_records),\n        },\n        \"mx\": mx_records,\n        \"email_security\": {\"spf\": spf, \"dmarc\": dmarc},\n        \"gravatar\": {\n            \"url\": gravatar_url if has_gravatar else None,\n            \"exists\": has_gravatar,\n        },\n    }\n"
  },
  {
    "path": "the_big_brother/modules/network_mapper.py",
    "content": "import socket\nimport asyncio\nimport requests\nfrom pyvis.network import Network\nimport tempfile\nimport os\nimport dns.resolver\n\nCOMMON_PORTS = {\n    21: \"FTP\", 22: \"SSH\", 23: \"Telnet\", 25: \"SMTP\", 53: \"DNS\", 80: \"HTTP\",\n    110: \"POP3\", 143: \"IMAP\", 443: \"HTTPS\", 465: \"SMTPS\", 587: \"SMTP-MSA\",\n    993: \"IMAPS\", 995: \"POP3S\", 1433: \"MSSQL\", 1521: \"Oracle DB\",\n    2082: \"cPanel\", 2083: \"cPanel HTTPS\", 3306: \"MySQL\", 3389: \"RDP\",\n    5432: \"PostgreSQL\", 5900: \"VNC\", 6379: \"Redis\", 8080: \"HTTP-Alt\",\n    8443: \"HTTPS-Alt\", 9200: \"Elasticsearch\", 27017: \"MongoDB\"\n}\n\nasync def check_port(ip, port):\n    conn = asyncio.open_connection(ip, port)\n    try:\n        reader, writer = await asyncio.wait_for(conn, timeout=1)\n        writer.close()\n        await writer.wait_closed()\n        return port, True\n    except:\n        return port, False\n\ndef get_geoip(ip):\n    try:\n        resp = requests.get(f\"http://ip-api.com/json/{ip}\", timeout=5)\n        if resp.status_code == 200:\n            return resp.json()\n    except:\n        pass\n    return {}\n\ndef get_rdap_whois(domain):\n    try:\n        # RDAP is the new JSON standard for WHOIS\n        resp = requests.get(f\"https://rdap.org/domain/{domain}\", timeout=5)\n        if resp.status_code == 200:\n            data = resp.json()\n            # Extract key info safely\n            return {\n                \"registrar\": data.get(\"entities\", [{}])[0].get(\"vcardArray\", [[],[]])[1][1][3] if \"entities\" in data else \"Unknown\",\n                \"creation_date\":  next((e[\"date\"] for e in data.get(\"events\", []) if e[\"eventAction\"] == \"registration\"), \"Unknown\"),\n                \"status\": data.get(\"status\", [])\n            }\n    except:\n        pass\n    return {}\n\ndef get_dns_records(domain):\n    records = {\"MX\": [], \"NS\": [], \"TXT\": [], \"A\": []}\n    try:\n        resolver = dns.resolver.Resolver()\n        resolver.timeout = 2\n        resolver.lifetime = 2\n        \n        try:\n            for r in resolver.resolve(domain, 'MX'):\n                records[\"MX\"].append(str(r.exchange))\n        except: pass\n        \n        try:\n            for r in resolver.resolve(domain, 'NS'):\n                records[\"NS\"].append(str(r.target))\n        except: pass\n        \n        try:\n            for r in resolver.resolve(domain, 'TXT'):\n                records[\"TXT\"].append(str(r))\n        except: pass\n\n        try:\n            for r in resolver.resolve(domain, 'A'):\n                records[\"A\"].append(str(r.address))\n        except: pass\n        \n    except Exception as e:\n        print(f\"DNS Error: {e}\")\n    return records\n\nasync def scan_target(domain: str):\n    \"\"\"\n    Scans a target for IP, open ports, subdomains, GeoIP, Whois, and DNS.\n    \"\"\"\n    results = {\n        \"domain\": domain,\n        \"ip\": None,\n        \"ports\": [],\n        \"subdomains\": [],\n        \"geoip\": {},\n        \"whois\": {},\n        \"dns\": {}\n    }\n    \n    # Resolve IP\n    try:\n        results[\"ip\"] = socket.gethostbyname(domain)\n    except:\n        return {\"error\": \"Could not resolve domain\"}\n        \n    # Parallel Tasks\n    # 1. Port Scan\n    port_tasks = [check_port(results[\"ip\"], p) for p in COMMON_PORTS.keys()]\n    \n    # 2. GeoIP (Sync but fast enough, or thread it)\n    results[\"geoip\"] = await asyncio.to_thread(get_geoip, results[\"ip\"])\n    \n    # 3. Whois\n    results[\"whois\"] = await asyncio.to_thread(get_rdap_whois, domain)\n    \n    # 4. DNS\n    results[\"dns\"] = await asyncio.to_thread(get_dns_records, domain)\n\n    # 5. Execute Port Scan\n    port_results = await asyncio.gather(*port_tasks)\n    \n    for port, is_open in port_results:\n        if is_open:\n            results[\"ports\"].append({\"port\": port, \"service\": COMMON_PORTS[port]})\n            \n    # 6. Subdomains (crt.sh)\n    try:\n        url = f\"https://crt.sh/?q=%.{domain}&output=json\"\n        resp = await asyncio.to_thread(requests.get, url, timeout=5)\n        if resp.status_code == 200:\n            data = resp.json()\n            subs = set()\n            for entry in data:\n                name = entry['name_value']\n                for n in name.split('\\n'):\n                    if n.endswith(domain) and n != domain and \"*\" not in n:\n                        subs.add(n)\n            results[\"subdomains\"] = list(subs)\n    except Exception as e:\n        print(f\"Subdomain Error: {e}\")\n        \n    return results\n\ndef generate_network_map(data):\n    \"\"\"\n    Generates an HTML network graph from scan data.\n    \"\"\"\n    net = Network(height=\"600px\", width=\"100%\", bgcolor=\"#0a0a0a\", font_color=\"white\")\n    \n    # Root Node\n    net.add_node(data[\"domain\"], label=data[\"domain\"], color=\"#00ff41\", shape=\"star\", size=30)\n    \n    # IP Node & Geo\n    if data.get(\"ip\"):\n        ip_label = f\"{data['ip']}\"\n        if data.get(\"geoip\"):\n            country = data[\"geoip\"].get(\"countryCode\", \"\")\n            isp = data[\"geoip\"].get(\"isp\", \"\")\n            ip_label += f\"\\n[{country}] {isp}\"\n            \n        net.add_node(data[\"ip\"], label=ip_label, color=\"#ffcc00\", shape=\"diamond\")\n        net.add_edge(data[\"domain\"], data[\"ip\"])\n        \n        # Ports\n        for p in data.get(\"ports\", []):\n            label = f\"{p['service']}:{p['port']}\"\n            net.add_node(label, label=label, color=\"#ff0000\", shape=\"dot\", size=10)\n            net.add_edge(data[\"ip\"], label)\n            \n    # DNS Nodes (MX, NS)\n    dns_data = data.get(\"dns\", {})\n    \n    for mx in dns_data.get(\"MX\", []):\n        label = f\"MX: {mx}\"\n        net.add_node(label, label=label, color=\"#00ccff\", shape=\"triangle\")\n        net.add_edge(data[\"domain\"], label)\n\n    for ns in dns_data.get(\"NS\", []):\n        label = f\"NS: {ns}\"\n        net.add_node(label, label=label, color=\"#ff00ff\", shape=\"triangle\")\n        net.add_edge(data[\"domain\"], label)\n            \n    # Subdomains (Cluster them if too many)\n    subs = data.get(\"subdomains\", [])\n    if len(subs) > 20: \n        # Create a cluster node\n        cluster_label = f\"+{len(subs)} SUBDOMAINS\"\n        net.add_node(\"subs_cluster\", label=cluster_label, color=\"#00cc00\", shape=\"hexagon\", size=20)\n        net.add_edge(data[\"domain\"], \"subs_cluster\")\n        # Connect first 5 explicitly\n        for sub in subs[:5]:\n            net.add_node(sub, label=sub, color=\"#00cc00\", shape=\"dot\", size=15)\n            net.add_edge(\"subs_cluster\", sub)\n    else:\n        for sub in subs:\n            net.add_node(sub, label=sub, color=\"#00cc00\", shape=\"dot\", size=15)\n            net.add_edge(data[\"domain\"], sub)\n        \n    # Physics options\n    net.force_atlas_2based()\n    \n    try:\n        return net.generate_html()\n    except:\n        return \"Error generating graph\"\n\n"
  },
  {
    "path": "the_big_brother/modules/paste_dragnet.py",
    "content": "\"\"\"\nPASTE DRAGNET — Hunts paste sites for a query using DuckDuckGo dorks.\nFalls back gracefully if duckduckgo-search is unavailable.\n\"\"\"\nimport asyncio\nfrom urllib.parse import quote\n\nimport requests\n\n\nPASTE_SITES = [\n    \"pastebin.com\",\n    \"ghostbin.co\",\n    \"rentry.co\",\n    \"controlc.com\",\n    \"paste.ee\",\n    \"0bin.net\",\n    \"justpaste.it\",\n    \"hastebin.com\",\n    \"ideone.com\",\n    \"dpaste.org\",\n    \"termbin.com\",\n    \"privatebin.net\",\n]\n\n\ndef _ddg_search(query: str, max_results: int = 15):\n    try:\n        from duckduckgo_search import DDGS\n        with DDGS(timeout=10) as ddgs:\n            return list(ddgs.text(query, max_results=max_results))\n    except Exception:\n        return []\n\n\ndef _hunt_site(query: str, site: str):\n    dork = f'site:{site} \"{query}\"'\n    results = _ddg_search(dork, max_results=10)\n    out = []\n    for r in results:\n        out.append({\n            \"title\": r.get(\"title\", \"\")[:160],\n            \"url\": r.get(\"href\") or r.get(\"url\"),\n            \"snippet\": (r.get(\"body\") or \"\")[:240],\n            \"site\": site,\n        })\n    return out\n\n\nasync def paste_dragnet(query: str) -> dict:\n    query = query.strip()\n    if not query:\n        return {\"error\": \"query required\"}\n\n    tasks = [asyncio.to_thread(_hunt_site, query, site) for site in PASTE_SITES]\n    results_per_site = await asyncio.gather(*tasks, return_exceptions=True)\n\n    all_hits = []\n    per_site = {}\n    for site, res in zip(PASTE_SITES, results_per_site):\n        if isinstance(res, Exception) or not res:\n            per_site[site] = 0\n            continue\n        per_site[site] = len(res)\n        all_hits.extend(res)\n\n    # Severity heuristic: keyword markers commonly seen in dumps\n    HOT_TERMS = [\"password\", \"passwd\", \"leak\", \"dump\", \"combo\", \"creds\", \"api_key\", \"secret\", \"token\"]\n    for hit in all_hits:\n        blob = f\"{hit.get('title','')} {hit.get('snippet','')}\".lower()\n        hit[\"severity\"] = \"CRITICAL\" if any(t in blob for t in HOT_TERMS) else \"LOW\"\n\n    all_hits.sort(key=lambda h: 0 if h[\"severity\"] == \"CRITICAL\" else 1)\n\n    return {\n        \"query\": query,\n        \"total_hits\": len(all_hits),\n        \"per_site\": per_site,\n        \"results\": all_hits[:120],\n    }\n"
  },
  {
    "path": "the_big_brother/modules/phantom_id.py",
    "content": "\"\"\"\nPHANTOM ID — Username enumeration across 200+ platforms.\nUses async HTTP with HEAD/GET requests against a curated sites list.\nReturns profile URLs, avatar detection hints, and a risk score.\n\"\"\"\n\nimport asyncio\nimport aiohttp\nimport hashlib\nfrom typing import Optional\n\n# Curated platform list with detection patterns\nPLATFORMS = [\n    # Social Networks\n    {\"name\": \"Instagram\", \"url\": \"https://www.instagram.com/{}/\", \"method\": \"GET\", \"cat\": \"social\"},\n    {\"name\": \"Twitter/X\", \"url\": \"https://twitter.com/{}\", \"method\": \"HEAD\", \"cat\": \"social\"},\n    {\"name\": \"TikTok\", \"url\": \"https://www.tiktok.com/@{}\", \"method\": \"HEAD\", \"cat\": \"social\"},\n    {\"name\": \"Facebook\", \"url\": \"https://www.facebook.com/{}\", \"method\": \"HEAD\", \"cat\": \"social\"},\n    {\"name\": \"Pinterest\", \"url\": \"https://www.pinterest.com/{}/\", \"method\": \"HEAD\", \"cat\": \"social\"},\n    {\"name\": \"Snapchat\", \"url\": \"https://www.snapchat.com/add/{}\", \"method\": \"GET\", \"not_found_text\": \"Sorry, we couldn't find that Snapchat account.\", \"cat\": \"social\"},\n    {\"name\": \"Reddit\", \"url\": \"https://www.reddit.com/user/{}\", \"method\": \"GET\", \"not_found_text\": \"page not found\", \"cat\": \"social\"},\n    {\"name\": \"Tumblr\", \"url\": \"https://{}.tumblr.com\", \"method\": \"HEAD\", \"cat\": \"social\"},\n    {\"name\": \"Discord (Lookup)\", \"url\": \"https://discord.com/users/{}\", \"method\": \"HEAD\", \"cat\": \"social\"},\n    {\"name\": \"VK\", \"url\": \"https://vk.com/{}\", \"method\": \"HEAD\", \"cat\": \"social\"},\n    {\"name\": \"Weibo\", \"url\": \"https://weibo.com/{}\", \"method\": \"HEAD\", \"cat\": \"social\"},\n    {\"name\": \"LinkedIn\", \"url\": \"https://www.linkedin.com/in/{}\", \"method\": \"HEAD\", \"cat\": \"professional\"},\n    # Tech / Dev\n    {\"name\": \"GitHub\", \"url\": \"https://github.com/{}\", \"method\": \"GET\", \"not_found_text\": \"not found\", \"cat\": \"tech\"},\n    {\"name\": \"GitLab\", \"url\": \"https://gitlab.com/{}\", \"method\": \"HEAD\", \"cat\": \"tech\"},\n    {\"name\": \"Bitbucket\", \"url\": \"https://bitbucket.org/{}\", \"method\": \"HEAD\", \"cat\": \"tech\"},\n    {\"name\": \"StackOverflow\", \"url\": \"https://stackoverflow.com/users/{}\", \"method\": \"HEAD\", \"cat\": \"tech\"},\n    {\"name\": \"HackerNews\", \"url\": \"https://news.ycombinator.com/user?id={}\", \"method\": \"GET\", \"not_found_text\": \"No such user\", \"cat\": \"tech\"},\n    {\"name\": \"Dev.to\", \"url\": \"https://dev.to/{}\", \"method\": \"HEAD\", \"cat\": \"tech\"},\n    {\"name\": \"Replit\", \"url\": \"https://replit.com/@{}\", \"method\": \"HEAD\", \"cat\": \"tech\"},\n    {\"name\": \"Codepen\", \"url\": \"https://codepen.io/{}\", \"method\": \"HEAD\", \"cat\": \"tech\"},\n    {\"name\": \"Kaggle\", \"url\": \"https://www.kaggle.com/{}\", \"method\": \"HEAD\", \"cat\": \"tech\"},\n    {\"name\": \"Npmjs\", \"url\": \"https://www.npmjs.com/~{}\", \"method\": \"HEAD\", \"cat\": \"tech\"},\n    {\"name\": \"PyPI\", \"url\": \"https://pypi.org/user/{}/\", \"method\": \"GET\", \"not_found_text\": \"404\", \"cat\": \"tech\"},\n    # Gaming\n    {\"name\": \"Steam\", \"url\": \"https://steamcommunity.com/id/{}\", \"method\": \"GET\", \"not_found_text\": \"error\", \"cat\": \"gaming\"},\n    {\"name\": \"Twitch\", \"url\": \"https://www.twitch.tv/{}\", \"method\": \"HEAD\", \"cat\": \"gaming\"},\n    {\"name\": \"Xbox\", \"url\": \"https://www.xboxgamertag.com/search/{}\", \"method\": \"HEAD\", \"cat\": \"gaming\"},\n    {\"name\": \"Roblox\", \"url\": \"https://www.roblox.com/user.aspx?username={}\", \"method\": \"HEAD\", \"cat\": \"gaming\"},\n    {\"name\": \"Minecraft (NameMC)\", \"url\": \"https://namemc.com/profile/{}\", \"method\": \"HEAD\", \"cat\": \"gaming\"},\n    {\"name\": \"Chess.com\", \"url\": \"https://www.chess.com/member/{}\", \"method\": \"HEAD\", \"cat\": \"gaming\"},\n    # Content / Creator\n    {\"name\": \"YouTube\", \"url\": \"https://www.youtube.com/@{}\", \"method\": \"HEAD\", \"cat\": \"content\"},\n    {\"name\": \"Medium\", \"url\": \"https://medium.com/@{}\", \"method\": \"HEAD\", \"cat\": \"content\"},\n    {\"name\": \"Substack\", \"url\": \"https://{}.substack.com\", \"method\": \"HEAD\", \"cat\": \"content\"},\n    {\"name\": \"Patreon\", \"url\": \"https://www.patreon.com/{}\", \"method\": \"HEAD\", \"cat\": \"content\"},\n    {\"name\": \"SoundCloud\", \"url\": \"https://soundcloud.com/{}\", \"method\": \"HEAD\", \"cat\": \"content\"},\n    {\"name\": \"Spotify\", \"url\": \"https://open.spotify.com/user/{}\", \"method\": \"HEAD\", \"cat\": \"content\"},\n    {\"name\": \"Bandcamp\", \"url\": \"https://{}.bandcamp.com\", \"method\": \"HEAD\", \"cat\": \"content\"},\n    {\"name\": \"Flickr\", \"url\": \"https://www.flickr.com/people/{}\", \"method\": \"HEAD\", \"cat\": \"content\"},\n    {\"name\": \"Vimeo\", \"url\": \"https://vimeo.com/{}\", \"method\": \"HEAD\", \"cat\": \"content\"},\n    {\"name\": \"Behance\", \"url\": \"https://www.behance.net/{}\", \"method\": \"HEAD\", \"cat\": \"content\"},\n    {\"name\": \"Dribbble\", \"url\": \"https://dribbble.com/{}\", \"method\": \"HEAD\", \"cat\": \"content\"},\n    {\"name\": \"500px\", \"url\": \"https://500px.com/p/{}\", \"method\": \"HEAD\", \"cat\": \"content\"},\n    # Forums / Community\n    {\"name\": \"Quora\", \"url\": \"https://www.quora.com/profile/{}\", \"method\": \"HEAD\", \"cat\": \"community\"},\n    {\"name\": \"Disqus\", \"url\": \"https://disqus.com/by/{}/\", \"method\": \"HEAD\", \"cat\": \"community\"},\n    {\"name\": \"ProductHunt\", \"url\": \"https://www.producthunt.com/@{}\", \"method\": \"HEAD\", \"cat\": \"community\"},\n    {\"name\": \"AngelList\", \"url\": \"https://angel.co/u/{}\", \"method\": \"HEAD\", \"cat\": \"professional\"},\n    {\"name\": \"Crunchbase\", \"url\": \"https://www.crunchbase.com/person/{}\", \"method\": \"HEAD\", \"cat\": \"professional\"},\n    # Misc / Identity\n    {\"name\": \"Gravatar\", \"url\": \"https://en.gravatar.com/{}\", \"method\": \"HEAD\", \"cat\": \"identity\"},\n    {\"name\": \"About.me\", \"url\": \"https://about.me/{}\", \"method\": \"HEAD\", \"cat\": \"identity\"},\n    {\"name\": \"Keybase\", \"url\": \"https://keybase.io/{}\", \"method\": \"GET\", \"not_found_text\": \"not found\", \"cat\": \"identity\"},\n    {\"name\": \"Linktree\", \"url\": \"https://linktr.ee/{}\", \"method\": \"HEAD\", \"cat\": \"identity\"},\n    {\"name\": \"Carrd\", \"url\": \"https://{}.carrd.co\", \"method\": \"HEAD\", \"cat\": \"identity\"},\n]\n\n\nasync def check_platform(session: aiohttp.ClientSession, platform: dict, username: str) -> Optional[dict]:\n    url = platform[\"url\"].format(username)\n    method = platform.get(\"method\", \"HEAD\")\n    not_found_text = platform.get(\"not_found_text\", \"\")\n\n    headers = {\n        \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/121.0.0.0 Safari/537.36\",\n        \"Accept-Language\": \"en-US,en;q=0.9\",\n    }\n\n    try:\n        timeout = aiohttp.ClientTimeout(total=8)\n        if method == \"GET\" and not_found_text:\n            async with session.get(url, headers=headers, timeout=timeout, allow_redirects=True, ssl=False) as resp:\n                if resp.status == 200:\n                    text = await resp.text(errors=\"ignore\")\n                    if not_found_text.lower() in text.lower():\n                        return None\n                    return {\"platform\": platform[\"name\"], \"url\": url, \"status\": resp.status, \"cat\": platform.get(\"cat\", \"misc\")}\n                return None\n        else:\n            async with session.head(url, headers=headers, timeout=timeout, allow_redirects=True, ssl=False) as resp:\n                if resp.status in (200, 301, 302, 307, 308):\n                    return {\"platform\": platform[\"name\"], \"url\": url, \"status\": resp.status, \"cat\": platform.get(\"cat\", \"misc\")}\n                return None\n    except Exception:\n        return None\n\n\nasync def phantom_id_search(username: str):\n    \"\"\"\n    Searches for a username across all configured platforms concurrently.\n    Returns found profiles, categories, and a risk score.\n    \"\"\"\n    results = []\n    connector = aiohttp.TCPConnector(ssl=False, limit=30)\n\n    async with aiohttp.ClientSession(connector=connector) as session:\n        tasks = [check_platform(session, p, username) for p in PLATFORMS]\n        responses = await asyncio.gather(*tasks, return_exceptions=True)\n\n    for r in responses:\n        if r and isinstance(r, dict):\n            results.append(r)\n\n    # Generate Gravatar avatar URL from username hash\n    username_hash = hashlib.md5(username.lower().encode()).hexdigest()\n    gravatar_url = f\"https://www.gravatar.com/avatar/{username_hash}?d=404&s=200\"\n\n    # Risk score: more platforms found = higher exposure score\n    total = len(PLATFORMS)\n    found = len(results)\n    risk_score = round((found / total) * 100, 1)\n\n    # Categorize results\n    cats = {}\n    for r in results:\n        c = r.get(\"cat\", \"misc\")\n        cats[c] = cats.get(c, 0) + 1\n\n    return {\n        \"username\": username,\n        \"found\": found,\n        \"total_checked\": total,\n        \"risk_score\": risk_score,\n        \"gravatar\": gravatar_url,\n        \"categories\": cats,\n        \"profiles\": results,\n    }\n"
  },
  {
    "path": "the_big_brother/modules/shadow_map.py",
    "content": "\"\"\"\nSHADOW MAP — IP/Domain threat intelligence and reputation analysis.\nQueries AbuseIPDB, VirusTotal (public API), Shodan, and URLhaus.\n\"\"\"\n\nimport asyncio\nimport aiohttp\nimport os\nimport socket\nfrom datetime import datetime\n\n\nABUSEIPDB_URL = \"https://api.abuseipdb.com/api/v2/check\"\nVIRUSTOTAL_URL = \"https://www.virustotal.com/api/v3/ip_addresses/{}\"\nVIRUSTOTAL_DOMAIN_URL = \"https://www.virustotal.com/api/v3/domains/{}\"\nURLHAUS_URL = \"https://urlhaus-api.abuse.ch/v1/host/\"\nSHODAN_URL = \"https://api.shodan.io/shodan/host/{}?key={}\"\nIPINFO_URL = \"https://ipinfo.io/{}/json\"\n\nABUSE_CATEGORIES = {\n    1: \"DNS Compromise\", 2: \"DNS Poisoning\", 3: \"Fraud Orders\", 4: \"DDoS Attack\",\n    5: \"FTP Brute-Force\", 6: \"Ping of Death\", 7: \"Phishing\", 8: \"Fraud VoIP\",\n    9: \"Open Proxy\", 10: \"Web Spam\", 11: \"Email Spam\", 12: \"Blog Spam\",\n    13: \"VPN IP\", 14: \"Port Scan\", 15: \"Hacking\", 16: \"SQL Injection\",\n    17: \"Spoofing\", 18: \"Brute-Force\", 19: \"Bad Web Bot\", 20: \"Exploited Host\",\n    21: \"Web App Attack\", 22: \"SSH\", 23: \"IoT Targeted\",\n}\n\n\ndef resolve_to_ip(target: str) -> str:\n    \"\"\"Resolve domain to IP if needed.\"\"\"\n    try:\n        socket.inet_aton(target)\n        return target  # Already an IP\n    except socket.error:\n        try:\n            return socket.gethostbyname(target)\n        except:\n            return target\n\n\nasync def check_abuseipdb(session: aiohttp.ClientSession, ip: str) -> dict:\n    \"\"\"Query AbuseIPDB for IP reputation.\"\"\"\n    api_key = os.environ.get(\"ABUSEIPDB_API_KEY\", \"\")\n    if not api_key:\n        return {\"error\": \"ABUSEIPDB_API_KEY not set\", \"available\": False}\n\n    headers = {\"Key\": api_key, \"Accept\": \"application/json\"}\n    params = {\"ipAddress\": ip, \"maxAgeInDays\": 90, \"verbose\": \"\"}\n\n    try:\n        async with session.get(ABUSEIPDB_URL, headers=headers, params=params, timeout=aiohttp.ClientTimeout(total=10), ssl=False) as resp:\n            if resp.status == 200:\n                data = (await resp.json()).get(\"data\", {})\n                category_ids = data.get(\"reports\", [{}])[-1].get(\"categories\", []) if data.get(\"reports\") else []\n                categories = [ABUSE_CATEGORIES.get(c, f\"Category {c}\") for c in category_ids]\n                return {\n                    \"available\": True,\n                    \"ip\": ip,\n                    \"abuse_score\": data.get(\"abuseConfidenceScore\", 0),\n                    \"country\": data.get(\"countryCode\"),\n                    \"isp\": data.get(\"isp\"),\n                    \"domain\": data.get(\"domain\"),\n                    \"is_tor\": data.get(\"isTor\", False),\n                    \"is_whitelisted\": data.get(\"isWhitelisted\", False),\n                    \"total_reports\": data.get(\"totalReports\", 0),\n                    \"last_reported\": data.get(\"lastReportedAt\"),\n                    \"categories\": categories,\n                    \"usage_type\": data.get(\"usageType\"),\n                }\n    except Exception as e:\n        return {\"error\": str(e), \"available\": False}\n    return {\"available\": False}\n\n\nasync def check_virustotal(session: aiohttp.ClientSession, target: str, is_ip: bool = True) -> dict:\n    \"\"\"Query VirusTotal for IP or domain reputation.\"\"\"\n    api_key = os.environ.get(\"VIRUSTOTAL_API_KEY\", \"\")\n    if not api_key:\n        return {\"error\": \"VIRUSTOTAL_API_KEY not set\", \"available\": False}\n\n    url = VIRUSTOTAL_URL.format(target) if is_ip else VIRUSTOTAL_DOMAIN_URL.format(target)\n    headers = {\"x-apikey\": api_key}\n\n    try:\n        async with session.get(url, headers=headers, timeout=aiohttp.ClientTimeout(total=10), ssl=False) as resp:\n            if resp.status == 200:\n                data = (await resp.json()).get(\"data\", {}).get(\"attributes\", {})\n                stats = data.get(\"last_analysis_stats\", {})\n                malicious = stats.get(\"malicious\", 0)\n                suspicious = stats.get(\"suspicious\", 0)\n                harmless = stats.get(\"harmless\", 0)\n                undetected = stats.get(\"undetected\", 0)\n                total = malicious + suspicious + harmless + undetected\n                return {\n                    \"available\": True,\n                    \"malicious\": malicious,\n                    \"suspicious\": suspicious,\n                    \"harmless\": harmless,\n                    \"undetected\": undetected,\n                    \"total_engines\": total,\n                    \"reputation\": data.get(\"reputation\", 0),\n                    \"tags\": data.get(\"tags\", []),\n                    \"country\": data.get(\"country\"),\n                    \"as_owner\": data.get(\"as_owner\"),\n                    \"threat_level\": \"CRITICAL\" if malicious > 5 else (\"HIGH\" if malicious > 0 else (\"SUSPICIOUS\" if suspicious > 0 else \"CLEAN\")),\n                }\n    except Exception as e:\n        return {\"error\": str(e), \"available\": False}\n    return {\"available\": False}\n\n\nasync def check_urlhaus(session: aiohttp.ClientSession, target: str) -> dict:\n    \"\"\"Query URLhaus for malicious URL data.\"\"\"\n    try:\n        async with session.post(URLHAUS_URL, data={\"host\": target}, timeout=aiohttp.ClientTimeout(total=8), ssl=False) as resp:\n            if resp.status == 200:\n                data = await resp.json()\n                if data.get(\"query_status\") == \"is_host\":\n                    urls = data.get(\"urls\", [])[:5]\n                    return {\n                        \"available\": True,\n                        \"found\": True,\n                        \"url_count\": len(data.get(\"urls\", [])),\n                        \"urls\": [\n                            {\"url\": u.get(\"url\"), \"status\": u.get(\"url_status\"), \"threat\": u.get(\"threat\"), \"date\": u.get(\"date_added\")}\n                            for u in urls\n                        ],\n                    }\n                return {\"available\": True, \"found\": False}\n    except Exception as e:\n        return {\"available\": True, \"found\": False, \"error\": str(e)}\n    return {\"available\": False}\n\n\nasync def check_ipinfo(session: aiohttp.ClientSession, ip: str) -> dict:\n    \"\"\"Get basic IP geolocation and ASN info from ipinfo.io (free tier).\"\"\"\n    try:\n        async with session.get(f\"https://ipinfo.io/{ip}/json\", timeout=aiohttp.ClientTimeout(total=8), ssl=False) as resp:\n            if resp.status == 200:\n                data = await resp.json()\n                return {\n                    \"ip\": data.get(\"ip\"),\n                    \"city\": data.get(\"city\"),\n                    \"region\": data.get(\"region\"),\n                    \"country\": data.get(\"country\"),\n                    \"org\": data.get(\"org\"),\n                    \"timezone\": data.get(\"timezone\"),\n                    \"loc\": data.get(\"loc\"),\n                    \"hostname\": data.get(\"hostname\"),\n                }\n    except Exception as e:\n        return {\"error\": str(e)}\n    return {}\n\n\nasync def shadow_map_analyze(target: str):\n    \"\"\"\n    Main SHADOW MAP entry: analyzes IP or domain for threat intelligence.\n    \"\"\"\n    # Detect if IP or domain\n    try:\n        socket.inet_aton(target)\n        is_ip = True\n        ip = target\n    except socket.error:\n        is_ip = False\n        ip = resolve_to_ip(target)\n\n    connector = aiohttp.TCPConnector(ssl=False, limit=10)\n    async with aiohttp.ClientSession(connector=connector) as session:\n        geo_task = check_ipinfo(session, ip)\n        abuse_task = check_abuseipdb(session, ip)\n        vt_task = check_virustotal(session, target, is_ip=is_ip)\n        urlhaus_task = check_urlhaus(session, target)\n\n        geo, abuse, vt, urlhaus = await asyncio.gather(\n            geo_task, abuse_task, vt_task, urlhaus_task,\n            return_exceptions=True\n        )\n\n    # Handle exceptions from gather\n    if isinstance(geo, Exception): geo = {}\n    if isinstance(abuse, Exception): abuse = {\"available\": False}\n    if isinstance(vt, Exception): vt = {\"available\": False}\n    if isinstance(urlhaus, Exception): urlhaus = {\"available\": False}\n\n    # Composite threat score (0-100)\n    score = 0\n    factors = []\n\n    if isinstance(abuse, dict) and abuse.get(\"available\"):\n        abuse_score = abuse.get(\"abuse_score\", 0)\n        score += int(abuse_score * 0.5)\n        if abuse_score > 50:\n            factors.append(f\"HIGH ABUSE SCORE ({abuse_score}%)\")\n        if abuse.get(\"is_tor\"):\n            score += 20\n            factors.append(\"TOR EXIT NODE\")\n\n    if isinstance(vt, dict) and vt.get(\"available\"):\n        malicious = vt.get(\"malicious\", 0)\n        suspicious = vt.get(\"suspicious\", 0)\n        score += min(malicious * 5 + suspicious * 2, 40)\n        if malicious > 0:\n            factors.append(f\"{malicious} VT ENGINES FLAGGED MALICIOUS\")\n        if suspicious > 0:\n            factors.append(f\"{suspicious} VT ENGINES FLAGGED SUSPICIOUS\")\n\n    if isinstance(urlhaus, dict) and urlhaus.get(\"found\"):\n        score += 15\n        factors.append(f\"FOUND IN URLHAUS ({urlhaus.get('url_count', 0)} MALICIOUS URLS)\")\n\n    score = min(score, 100)\n    threat_level = \"CRITICAL\" if score >= 80 else (\"HIGH\" if score >= 50 else (\"MEDIUM\" if score >= 20 else \"CLEAN\"))\n\n    return {\n        \"target\": target,\n        \"resolved_ip\": ip,\n        \"is_ip\": is_ip,\n        \"threat_score\": score,\n        \"threat_level\": threat_level,\n        \"threat_factors\": factors,\n        \"geo\": geo if isinstance(geo, dict) else {},\n        \"abuseipdb\": abuse if isinstance(abuse, dict) else {},\n        \"virustotal\": vt if isinstance(vt, dict) else {},\n        \"urlhaus\": urlhaus if isinstance(urlhaus, dict) else {},\n    }\n"
  },
  {
    "path": "the_big_brother/modules/sigint_sweep.py",
    "content": "\"\"\"\nSIGINT SWEEP — Social Intelligence gathering.\nFetches Reddit posts, Google News RSS, and Hacker News mentions\nfor a given keyword/target. Returns a ranked feed with sentiment hints.\n\"\"\"\n\nimport asyncio\nimport aiohttp\nimport xml.etree.ElementTree as ET\nfrom datetime import datetime\nimport re\n\n\nPOSITIVE_WORDS = {\"great\", \"good\", \"love\", \"awesome\", \"best\", \"win\", \"launch\", \"growth\", \"success\", \"excellent\", \"top\", \"hire\", \"partner\"}\nNEGATIVE_WORDS = {\"hack\", \"breach\", \"leak\", \"fail\", \"bad\", \"worst\", \"scam\", \"fraud\", \"attack\", \"lawsuit\", \"fine\", \"exposed\", \"stolen\", \"malware\", \"ransomware\", \"phish\"}\n\n\ndef detect_sentiment(text: str) -> str:\n    text_lower = text.lower()\n    neg = sum(1 for w in NEGATIVE_WORDS if w in text_lower)\n    pos = sum(1 for w in POSITIVE_WORDS if w in text_lower)\n    if neg > pos:\n        return \"NEGATIVE\"\n    if pos > neg:\n        return \"POSITIVE\"\n    return \"NEUTRAL\"\n\n\ndef clean_html(raw: str) -> str:\n    clean = re.sub(r'<[^>]+>', '', raw)\n    clean = re.sub(r'&amp;', '&', clean)\n    clean = re.sub(r'&lt;', '<', clean)\n    clean = re.sub(r'&gt;', '>', clean)\n    clean = re.sub(r'&quot;', '\"', clean)\n    clean = re.sub(r'\\s+', ' ', clean).strip()\n    return clean[:300]\n\n\nasync def fetch_reddit(session: aiohttp.ClientSession, query: str) -> list:\n    \"\"\"Fetch from Reddit's JSON API (no auth needed for public search).\"\"\"\n    results = []\n    headers = {\"User-Agent\": \"TheBigBrotherV4:OSINT:1.0\"}\n    url = f\"https://www.reddit.com/search.json?q={query}&sort=new&limit=15&type=link\"\n    try:\n        async with session.get(url, headers=headers, timeout=aiohttp.ClientTimeout(total=10), ssl=False) as resp:\n            if resp.status == 200:\n                data = await resp.json()\n                posts = data.get(\"data\", {}).get(\"children\", [])\n                for post in posts:\n                    p = post.get(\"data\", {})\n                    title = p.get(\"title\", \"\")\n                    text = p.get(\"selftext\", \"\")[:200]\n                    results.append({\n                        \"source\": \"Reddit\",\n                        \"subreddit\": f\"r/{p.get('subreddit', '')}\",\n                        \"title\": title,\n                        \"snippet\": text or title,\n                        \"url\": f\"https://reddit.com{p.get('permalink', '')}\",\n                        \"score\": p.get(\"score\", 0),\n                        \"comments\": p.get(\"num_comments\", 0),\n                        \"date\": datetime.utcfromtimestamp(p.get(\"created_utc\", 0)).strftime(\"%Y-%m-%d\"),\n                        \"sentiment\": detect_sentiment(title + \" \" + text),\n                    })\n    except Exception as e:\n        print(f\"Reddit fetch error: {e}\")\n    return results\n\n\nasync def fetch_google_news(session: aiohttp.ClientSession, query: str) -> list:\n    \"\"\"Fetch from Google News RSS feed (no auth needed, public).\"\"\"\n    results = []\n    url = f\"https://news.google.com/rss/search?q={query}&hl=en-US&gl=US&ceid=US:en\"\n    try:\n        async with session.get(url, timeout=aiohttp.ClientTimeout(total=10), ssl=False) as resp:\n            if resp.status == 200:\n                text = await resp.text()\n                root = ET.fromstring(text)\n                channel = root.find(\"channel\")\n                if channel is not None:\n                    for item in channel.findall(\"item\")[:15]:\n                        title = item.findtext(\"title\", \"\")\n                        link = item.findtext(\"link\", \"\")\n                        pub_date = item.findtext(\"pubDate\", \"\")\n                        source_el = item.find(\"source\")\n                        source_name = source_el.text if source_el is not None else \"News\"\n                        description = clean_html(item.findtext(\"description\", \"\"))\n                        results.append({\n                            \"source\": \"News\",\n                            \"outlet\": source_name,\n                            \"title\": title,\n                            \"snippet\": description,\n                            \"url\": link,\n                            \"date\": pub_date[:16] if pub_date else \"\",\n                            \"sentiment\": detect_sentiment(title + \" \" + description),\n                        })\n    except Exception as e:\n        print(f\"Google News fetch error: {e}\")\n    return results\n\n\nasync def fetch_hackernews(session: aiohttp.ClientSession, query: str) -> list:\n    \"\"\"Search Hacker News via Algolia API (public, no auth).\"\"\"\n    results = []\n    url = f\"https://hn.algolia.com/api/v1/search?query={query}&tags=story&hitsPerPage=10\"\n    try:\n        async with session.get(url, timeout=aiohttp.ClientTimeout(total=8), ssl=False) as resp:\n            if resp.status == 200:\n                data = await resp.json()\n                for hit in data.get(\"hits\", []):\n                    title = hit.get(\"title\", \"\")\n                    ts = hit.get(\"created_at\", \"\")[:10]\n                    results.append({\n                        \"source\": \"HackerNews\",\n                        \"subreddit\": \"HN\",\n                        \"title\": title,\n                        \"snippet\": hit.get(\"story_text\", \"\")[:200] or title,\n                        \"url\": hit.get(\"url\") or f\"https://news.ycombinator.com/item?id={hit.get('objectID')}\",\n                        \"score\": hit.get(\"points\", 0),\n                        \"comments\": hit.get(\"num_comments\", 0),\n                        \"date\": ts,\n                        \"sentiment\": detect_sentiment(title),\n                    })\n    except Exception as e:\n        print(f\"HackerNews fetch error: {e}\")\n    return results\n\n\nasync def fetch_twitter_nitter(session: aiohttp.ClientSession, query: str) -> list:\n    \"\"\"Fetch recent tweets via public Nitter RSS instances.\"\"\"\n    results = []\n    nitter_instances = [\n        \"https://nitter.poast.org\",\n        \"https://nitter.privacydev.net\",\n        \"https://nitter.1d4.us\",\n    ]\n    for instance in nitter_instances:\n        url = f\"{instance}/search/rss?q={query}&f=tweets\"\n        try:\n            async with session.get(url, timeout=aiohttp.ClientTimeout(total=6), ssl=False) as resp:\n                if resp.status == 200:\n                    text = await resp.text()\n                    root = ET.fromstring(text)\n                    channel = root.find(\"channel\")\n                    if channel is not None:\n                        for item in channel.findall(\"item\")[:10]:\n                            title = item.findtext(\"title\", \"\")\n                            link = item.findtext(\"link\", \"\")\n                            pub_date = item.findtext(\"pubDate\", \"\")\n                            creator = item.findtext(\"{http://purl.org/dc/elements/1.1/}creator\", \"\")\n                            results.append({\n                                \"source\": \"Twitter/X\",\n                                \"subreddit\": creator,\n                                \"title\": title,\n                                \"snippet\": clean_html(title)[:200],\n                                \"url\": link.replace(instance, \"https://twitter.com\"),\n                                \"score\": 0,\n                                \"comments\": 0,\n                                \"date\": pub_date[:16] if pub_date else \"\",\n                                \"sentiment\": detect_sentiment(title),\n                            })\n                    if results:\n                        break  # Got results from this instance\n        except Exception:\n            continue\n    return results\n\n\nasync def sigint_sweep(query: str):\n    \"\"\"\n    Main SIGINT SWEEP entry point.\n    Returns merged feed from Reddit, Google News, HackerNews, and Twitter/X.\n    \"\"\"\n    connector = aiohttp.TCPConnector(ssl=False, limit=20)\n    async with aiohttp.ClientSession(connector=connector) as session:\n        reddit_results, news_results, hn_results, twitter_results = await asyncio.gather(\n            fetch_reddit(session, query),\n            fetch_google_news(session, query),\n            fetch_hackernews(session, query),\n            fetch_twitter_nitter(session, query),\n        )\n\n    all_results = reddit_results + news_results + hn_results + twitter_results\n\n    # Sentiment summary\n    sentiments = {\"POSITIVE\": 0, \"NEGATIVE\": 0, \"NEUTRAL\": 0}\n    for item in all_results:\n        sentiments[item.get(\"sentiment\", \"NEUTRAL\")] += 1\n\n    return {\n        \"query\": query,\n        \"total\": len(all_results),\n        \"sources\": {\n            \"reddit\": len(reddit_results),\n            \"news\": len(news_results),\n            \"hackernews\": len(hn_results),\n            \"twitter\": len(twitter_results),\n        },\n        \"sentiment_summary\": sentiments,\n        \"feed\": all_results,\n    }\n"
  },
  {
    "path": "the_big_brother/modules/ssl_sentinel.py",
    "content": "import ssl\nimport socket\nimport datetime\n\ndef get_ssl_info(domain: str):\n    \"\"\"\n    Connects to a domain and retrieves SSL certificate details.\n    \"\"\"\n    ctx = ssl.create_default_context()\n    results = {\n        \"domain\": domain,\n        \"issuer\": {},\n        \"subject\": {},\n        \"sans\": [],\n        \"not_before\": \"\",\n        \"not_after\": \"\",\n        \"expired\": False,\n        \"error\": None\n    }\n    \n    try:\n        with socket.create_connection((domain, 443), timeout=10) as sock:\n            with ctx.wrap_socket(sock, server_hostname=domain) as ssock:\n                cert = ssock.getpeercert()\n                \n                # Extract Issuer\n                for item in cert.get('issuer', []):\n                    key, val = item[0]\n                    results['issuer'][key] = val\n                    \n                # Extract Subject\n                for item in cert.get('subject', []):\n                    key, val = item[0]\n                    results['subject'][key] = val\n                \n                # Extract SANs (Subject Alternative Names)\n                # These are gold mines for subdomains\n                sans = cert.get('subjectAltName', [])\n                results['sans'] = [val for key, val in sans if key == 'DNS']\n                \n                # Dates\n                results['not_before'] = cert.get('notBefore', '')\n                results['not_after'] = cert.get('notAfter', '')\n                \n                # Check Expiry\n                if results['not_after']:\n                    # Format: May 25 12:00:00 2026 GMT\n                    # Python ssl usually returns this format\n                    try:\n                        expire_date = datetime.datetime.strptime(results['not_after'], \"%b %d %H:%M:%S %Y %Z\")\n                        if expire_date < datetime.datetime.utcnow():\n                            results['expired'] = True\n                    except:\n                        pass\n                        \n    except Exception as e:\n        results['error'] = str(e)\n        \n    return results\n"
  },
  {
    "path": "the_big_brother/modules/wayback_spectre.py",
    "content": "\"\"\"\nWAYBACK SPECTRE — Wayback Machine CDX timeline + sensitive-path flagging.\nUses the free CDX server to enumerate every archived snapshot, then surfaces\nhistorical paths that often leak data (admin/, .env, .git, backup files, etc).\n\"\"\"\nimport asyncio\nfrom collections import Counter\nfrom urllib.parse import urlparse\n\nimport requests\n\n\nSENSITIVE_PATTERNS = [\n    r\"/admin\", r\"/login\", r\"/wp-admin\", r\"/wp-login\", r\"/phpmyadmin\",\n    r\"\\.env\", r\"\\.git\", r\"\\.svn\", r\"\\.htaccess\", r\"\\.htpasswd\",\n    r\"/config\", r\"/backup\", r\"/dump\", r\"/install\", r\"/setup\",\n    r\"\\.bak$\", r\"\\.old$\", r\"\\.sql$\", r\"\\.zip$\", r\"\\.tar\\.gz$\",\n    r\"/api/internal\", r\"/debug\", r\"/test\", r\"/private\", r\"/secret\",\n    r\"id_rsa\", r\"\\.pem$\", r\"\\.key$\", r\"credentials\", r\"/dashboard\",\n]\n\n\ndef _cdx(domain: str, limit: int = 5000):\n    url = \"https://web.archive.org/cdx/search/cdx\"\n    params = {\n        \"url\": f\"{domain}/*\",\n        \"output\": \"json\",\n        \"limit\": limit,\n        \"fl\": \"timestamp,original,mimetype,statuscode,digest\",\n        \"filter\": \"statuscode:200\",\n        \"collapse\": \"urlkey\",\n    }\n    try:\n        r = requests.get(url, params=params, timeout=15)\n        if r.status_code != 200:\n            return []\n        rows = r.json()\n        if not rows or len(rows) < 2:\n            return []\n        header = rows[0]\n        return [dict(zip(header, row)) for row in rows[1:]]\n    except Exception:\n        return []\n\n\ndef _flag_sensitive(rows: list) -> list:\n    import re\n    flagged = []\n    seen = set()\n    for row in rows:\n        url = row[\"original\"]\n        for pat in SENSITIVE_PATTERNS:\n            if re.search(pat, url, re.IGNORECASE):\n                key = (url, pat)\n                if key in seen:\n                    continue\n                seen.add(key)\n                ts = row[\"timestamp\"]\n                flagged.append({\n                    \"url\": url,\n                    \"pattern\": pat,\n                    \"timestamp\": ts,\n                    \"snapshot\": f\"https://web.archive.org/web/{ts}/{url}\",\n                    \"mime\": row.get(\"mimetype\", \"\"),\n                })\n                break\n    return flagged[:80]\n\n\ndef _yearly_buckets(rows: list) -> list:\n    by_year = Counter()\n    for r in rows:\n        ts = r[\"timestamp\"]\n        if len(ts) >= 4:\n            by_year[ts[:4]] += 1\n    return [{\"year\": y, \"count\": c} for y, c in sorted(by_year.items())]\n\n\ndef _mime_buckets(rows: list) -> list:\n    c = Counter(r.get(\"mimetype\", \"unknown\") for r in rows)\n    return [{\"mime\": m, \"count\": n} for m, n in c.most_common(10)]\n\n\nasync def wayback_spectre(target: str) -> dict:\n    target = target.strip()\n    if target.startswith((\"http://\", \"https://\")):\n        target = urlparse(target).netloc\n    if not target:\n        return {\"error\": \"Invalid target\"}\n\n    rows = await asyncio.to_thread(_cdx, target)\n    if not rows:\n        return {\n            \"target\": target,\n            \"total_snapshots\": 0,\n            \"first_seen\": None,\n            \"last_seen\": None,\n            \"yearly\": [],\n            \"mimes\": [],\n            \"sensitive\": [],\n            \"sample\": [],\n            \"message\": \"No snapshots found in Wayback Machine.\",\n        }\n\n    first = min(rows, key=lambda r: r[\"timestamp\"])\n    last = max(rows, key=lambda r: r[\"timestamp\"])\n\n    sensitive = _flag_sensitive(rows)\n    yearly = _yearly_buckets(rows)\n    mimes = _mime_buckets(rows)\n\n    sample = []\n    seen = set()\n    for r in rows:\n        u = r[\"original\"]\n        if u in seen:\n            continue\n        seen.add(u)\n        sample.append({\n            \"url\": u,\n            \"timestamp\": r[\"timestamp\"],\n            \"snapshot\": f\"https://web.archive.org/web/{r['timestamp']}/{u}\",\n        })\n        if len(sample) >= 50:\n            break\n\n    return {\n        \"target\": target,\n        \"total_snapshots\": len(rows),\n        \"first_seen\": first[\"timestamp\"],\n        \"last_seen\": last[\"timestamp\"],\n        \"first_url\": first[\"original\"],\n        \"yearly\": yearly,\n        \"mimes\": mimes,\n        \"sensitive_count\": len(sensitive),\n        \"sensitive\": sensitive,\n        \"sample\": sample,\n    }\n"
  },
  {
    "path": "the_big_brother/notify.py",
    "content": "\"\"\"The Big Brother Notify Module\n\nThis module defines the objects for notifying the caller about the\nresults of queries.\n\"\"\"\nfrom the_big_brother.result import QueryStatus\nfrom colorama import Fore, Style\nimport webbrowser\n\n# Global variable to count the number of results.\nglobvar = 0\n\n\nclass QueryNotify:\n    \"\"\"Query Notify Object.\n\n    Base class that describes methods available to notify the results of\n    a query.\n    It is intended that other classes inherit from this base class and\n    override the methods to implement specific functionality.\n    \"\"\"\n\n    def __init__(self, result=None):\n        \"\"\"Create Query Notify Object.\n\n        Contains information about a specific method of notifying the results\n        of a query.\n\n        Keyword Arguments:\n        self                   -- This object.\n        result                 -- Object of type QueryResult() containing\n                                  results for this query.\n\n        Return Value:\n        Nothing.\n        \"\"\"\n\n        self.result = result\n\n        # return\n\n    def start(self, message=None):\n        \"\"\"Notify Start.\n\n        Notify method for start of query.  This method will be called before\n        any queries are performed.  This method will typically be\n        overridden by higher level classes that will inherit from it.\n\n        Keyword Arguments:\n        self                   -- This object.\n        message                -- Object that is used to give context to start\n                                  of query.\n                                  Default is None.\n\n        Return Value:\n        Nothing.\n        \"\"\"\n\n        # return\n\n    def update(self, result):\n        \"\"\"Notify Update.\n\n        Notify method for query result.  This method will typically be\n        overridden by higher level classes that will inherit from it.\n\n        Keyword Arguments:\n        self                   -- This object.\n        result                 -- Object of type QueryResult() containing\n                                  results for this query.\n\n        Return Value:\n        Nothing.\n        \"\"\"\n\n        self.result = result\n\n        # return\n\n    def finish(self, message=None):\n        \"\"\"Notify Finish.\n\n        Notify method for finish of query.  This method will be called after\n        all queries have been performed.  This method will typically be\n        overridden by higher level classes that will inherit from it.\n\n        Keyword Arguments:\n        self                   -- This object.\n        message                -- Object that is used to give context to start\n                                  of query.\n                                  Default is None.\n\n        Return Value:\n        Nothing.\n        \"\"\"\n\n        # return\n\n    def __str__(self):\n        \"\"\"Convert Object To String.\n\n        Keyword Arguments:\n        self                   -- This object.\n\n        Return Value:\n        Nicely formatted string to get information about this object.\n        \"\"\"\n        return str(self.result)\n\n\nclass QueryNotifyPrint(QueryNotify):\n    \"\"\"Query Notify Print Object.\n\n    Query notify class that prints results.\n    \"\"\"\n\n    def __init__(self, result=None, verbose=False, print_all=False, browse=False):\n        \"\"\"Create Query Notify Print Object.\n\n        Contains information about a specific method of notifying the results\n        of a query.\n\n        Keyword Arguments:\n        self                   -- This object.\n        result                 -- Object of type QueryResult() containing\n                                  results for this query.\n        verbose                -- Boolean indicating whether to give verbose output.\n        print_all              -- Boolean indicating whether to only print all sites, including not found.\n        browse                 -- Boolean indicating whether to open found sites in a web browser.\n\n        Return Value:\n        Nothing.\n        \"\"\"\n\n        super().__init__(result)\n        self.verbose = verbose\n        self.print_all = print_all\n        self.browse = browse\n\n        return\n\n    def start(self, message):\n        \"\"\"Notify Start.\n\n        Will print the title to the standard output.\n\n        Keyword Arguments:\n        self                   -- This object.\n        message                -- String containing username that the series\n                                  of queries are about.\n\n        Return Value:\n        Nothing.\n        \"\"\"\n\n        title = \"Checking username\"\n\n        print(Style.BRIGHT + Fore.GREEN + \"[\" +\n              Fore.YELLOW + \"*\" +\n              Fore.GREEN + f\"] {title}\" +\n              Fore.WHITE + f\" {message}\" +\n              Fore.GREEN + \" on:\")\n        # An empty line between first line and the result(more clear output)\n        print('\\r')\n\n        return\n\n    def countResults(self):\n        \"\"\"This function counts the number of results. Every time the function is called,\n        the number of results is increasing.\n\n        Keyword Arguments:\n        self                   -- This object.\n\n        Return Value:\n        The number of results by the time we call the function.\n        \"\"\"\n        global globvar\n        globvar += 1\n        return globvar\n\n    def update(self, result):\n        \"\"\"Notify Update.\n\n        Will print the query result to the standard output.\n\n        Keyword Arguments:\n        self                   -- This object.\n        result                 -- Object of type QueryResult() containing\n                                  results for this query.\n\n        Return Value:\n        Nothing.\n        \"\"\"\n        self.result = result\n\n        response_time_text = \"\"\n        if self.result.query_time is not None and self.verbose is True:\n            response_time_text = f\" [{round(self.result.query_time * 1000)}ms]\"\n\n        # Output to the terminal is desired.\n        if result.status == QueryStatus.CLAIMED:\n            self.countResults()\n            print(Style.BRIGHT + Fore.WHITE + \"[\" +\n                  Fore.GREEN + \"+\" +\n                  Fore.WHITE + \"]\" +\n                  response_time_text +\n                  Fore.GREEN +\n                  f\" {self.result.site_name}: \" +\n                  Style.RESET_ALL +\n                  f\"{self.result.site_url_user}\")\n            if self.browse:\n                webbrowser.open(self.result.site_url_user, 2)\n\n        elif result.status == QueryStatus.AVAILABLE:\n            if self.print_all:\n                print(Style.BRIGHT + Fore.WHITE + \"[\" +\n                      Fore.RED + \"-\" +\n                      Fore.WHITE + \"]\" +\n                      response_time_text +\n                      Fore.GREEN + f\" {self.result.site_name}:\" +\n                      Fore.YELLOW + \" Not Found!\")\n\n        elif result.status == QueryStatus.UNKNOWN:\n            if self.print_all:\n                print(Style.BRIGHT + Fore.WHITE + \"[\" +\n                      Fore.RED + \"-\" +\n                      Fore.WHITE + \"]\" +\n                      Fore.GREEN + f\" {self.result.site_name}:\" +\n                      Fore.RED + f\" {self.result.context}\" +\n                      Fore.YELLOW + \" \")\n\n        elif result.status == QueryStatus.ILLEGAL:\n            if self.print_all:\n                msg = \"Illegal Username Format For This Site!\"\n                print(Style.BRIGHT + Fore.WHITE + \"[\" +\n                      Fore.RED + \"-\" +\n                      Fore.WHITE + \"]\" +\n                      Fore.GREEN + f\" {self.result.site_name}:\" +\n                      Fore.YELLOW + f\" {msg}\")\n                \n        elif result.status == QueryStatus.WAF:\n            if self.print_all:\n                print(Style.BRIGHT + Fore.WHITE + \"[\" +\n                      Fore.RED + \"-\" +\n                      Fore.WHITE + \"]\" +\n                      Fore.GREEN + f\" {self.result.site_name}:\" +\n                      Fore.RED + \" Blocked by bot detection\" +\n                      Fore.YELLOW + \" (proxy may help)\")\n\n        else:\n            # It should be impossible to ever get here...\n            raise ValueError(\n                f\"Unknown Query Status '{result.status}' for site '{self.result.site_name}'\"\n            )\n\n        return\n\n    def finish(self, message=\"The processing has been finished.\"):\n        \"\"\"Notify Start.\n        Will print the last line to the standard output.\n        Keyword Arguments:\n        self                   -- This object.\n        message                -- The 2 last phrases.\n        Return Value:\n        Nothing.\n        \"\"\"\n        NumberOfResults = self.countResults() - 1\n\n        print(Style.BRIGHT + Fore.GREEN + \"[\" +\n              Fore.YELLOW + \"*\" +\n              Fore.GREEN + \"] Search completed with\" +\n              Fore.WHITE + f\" {NumberOfResults} \" +\n              Fore.GREEN + \"results\" + Style.RESET_ALL\n              )\n\n    def __str__(self):\n        \"\"\"Convert Object To String.\n\n        Keyword Arguments:\n        self                   -- This object.\n\n        Return Value:\n        Nicely formatted string to get information about this object.\n        \"\"\"\n        return str(self.result)\n"
  },
  {
    "path": "the_big_brother/py.typed",
    "content": ""
  },
  {
    "path": "the_big_brother/resources/data.json",
    "content": "{\n  \"$schema\": \"data.schema.json\",\n  \"1337x\": {\n    \"errorMsg\": [\n      \"<title>Error something went wrong.</title>\",\n      \"<head><title>404 Not Found</title></head>\"\n    ],\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[A-Za-z0-9]{4,12}$\",\n    \"url\": \"https://www.1337x.to/user/{}/\",\n    \"urlMain\": \"https://www.1337x.to/\",\n    \"username_claimed\": \"FitGirl\"\n  },\n  \"2Dimensions\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://2Dimensions.com/a/{}\",\n    \"urlMain\": \"https://2Dimensions.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"7Cups\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.7cups.com/@{}\",\n    \"urlMain\": \"https://www.7cups.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"9GAG\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.9gag.com/u/{}\",\n    \"urlMain\": \"https://www.9gag.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"APClips\": {\n    \"errorMsg\": \"Amateur Porn Content Creators\",\n    \"errorType\": \"message\",\n    \"isNSFW\": true,\n    \"url\": \"https://apclips.com/{}\",\n    \"urlMain\": \"https://apclips.com/\",\n    \"username_claimed\": \"onlybbyraq\"\n  },\n  \"About.me\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://about.me/{}\",\n    \"urlMain\": \"https://about.me/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Academia.edu\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[^.]*$\",\n    \"url\": \"https://independent.academia.edu/{}\",\n    \"urlMain\": \"https://www.academia.edu/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"AdmireMe.Vip\": {\n    \"errorMsg\": \"Page Not Found\",\n    \"errorType\": \"message\",\n    \"isNSFW\": true,\n    \"url\": \"https://admireme.vip/{}\",\n    \"urlMain\": \"https://admireme.vip/\",\n    \"username_claimed\": \"DemiDevil\"\n  },\n  \"Airbit\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://airbit.com/{}\",\n    \"urlMain\": \"https://airbit.com/\",\n    \"username_claimed\": \"airbit\"\n  },\n  \"Airliners\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.airliners.net/user/{}/profile/photos\",\n    \"urlMain\": \"https://www.airliners.net/\",\n    \"username_claimed\": \"yushinlin\"\n  },\n  \"All Things Worn\": {\n    \"errorMsg\": \"Sell Used Panties\",\n    \"errorType\": \"message\",\n    \"isNSFW\": true,\n    \"url\": \"https://www.allthingsworn.com/profile/{}\",\n    \"urlMain\": \"https://www.allthingsworn.com\",\n    \"username_claimed\": \"pink\"\n  },\n  \"AllMyLinks\": {\n    \"errorMsg\": \"Page not found\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[a-z0-9][a-z0-9-]{2,32}$\",\n    \"url\": \"https://allmylinks.com/{}\",\n    \"urlMain\": \"https://allmylinks.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"AniWorld\": {\n    \"errorMsg\": \"Dieses Profil ist nicht verf\\u00fcgbar\",\n    \"errorType\": \"message\",\n    \"url\": \"https://aniworld.to/user/profil/{}\",\n    \"urlMain\": \"https://aniworld.to/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Anilist\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[A-Za-z0-9]{2,20}$\",\n    \"request_method\": \"POST\",\n    \"request_payload\": {\n      \"query\": \"query($name:String){User(name:$name){id}}\",\n      \"variables\": {\n        \"name\": \"{}\"\n      }\n    },\n    \"url\": \"https://anilist.co/user/{}/\",\n    \"urlMain\": \"https://anilist.co/\",\n    \"urlProbe\": \"https://graphql.anilist.co/\",\n    \"username_claimed\": \"Josh\"\n  },\n  \"Apple Developer\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://developer.apple.com/forums/profile/{}\",\n    \"urlMain\": \"https://developer.apple.com\",\n    \"username_claimed\": \"lio24d\"\n  },\n  \"Apple Discussions\": {\n    \"errorMsg\": \"Looking for something in Apple Support Communities?\",\n    \"errorType\": \"message\",\n    \"url\": \"https://discussions.apple.com/profile/{}\",\n    \"urlMain\": \"https://discussions.apple.com\",\n    \"username_claimed\": \"jason\"\n  },\n  \"Aparat\": {\n    \"errorType\": \"status_code\",\n    \"request_method\": \"GET\",\n    \"url\": \"https://www.aparat.com/{}/\",\n    \"urlMain\": \"https://www.aparat.com/\",\n    \"urlProbe\": \"https://www.aparat.com/api/fa/v1/user/user/information/username/{}\",\n    \"username_claimed\": \"jadi\"\n  },\n  \"Archive of Our Own\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[^.]*?$\",\n    \"url\": \"https://archiveofourown.org/users/{}\",\n    \"urlMain\": \"https://archiveofourown.org/\",\n    \"username_claimed\": \"test\"\n  },\n  \"Archive.org\": {\n    \"__comment__\": \"'The resource could not be found' relates to archive downtime\",\n    \"errorMsg\": [\n      \"could not fetch an account with user item identifier\",\n      \"The resource could not be found\",\n      \"Internet Archive services are temporarily offline\"\n    ],\n    \"errorType\": \"message\",\n    \"url\": \"https://archive.org/details/@{}\",\n    \"urlMain\": \"https://archive.org\",\n    \"urlProbe\": \"https://archive.org/details/@{}?noscript=true\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Arduino Forum\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://forum.arduino.cc/u/{}/summary\",\n    \"urlMain\": \"https://forum.arduino.cc/\",\n    \"username_claimed\": \"system\"\n  },\n  \"ArtStation\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.artstation.com/{}\",\n    \"urlMain\": \"https://www.artstation.com/\",\n    \"username_claimed\": \"Blue\"\n  },\n  \"Asciinema\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://asciinema.org/~{}\",\n    \"urlMain\": \"https://asciinema.org\",\n    \"username_claimed\": \"red\"\n  },\n  \"Ask Fedora\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://ask.fedoraproject.org/u/{}\",\n    \"urlMain\": \"https://ask.fedoraproject.org/\",\n    \"username_claimed\": \"red\"\n  },\n  \"Atcoder\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://atcoder.jp/users/{}\",\n    \"urlMain\": \"https://atcoder.jp/\",\n    \"username_claimed\": \"ksun48\"\n  },\n  \"Vjudge\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://VJudge.net/user/{}\",\n    \"urlMain\": \"https://VJudge.net/\",\n    \"username_claimed\": \"tokitsukaze\"\n  },\n  \"Audiojungle\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z0-9_]+$\",\n    \"url\": \"https://audiojungle.net/user/{}\",\n    \"urlMain\": \"https://audiojungle.net/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Autofrage\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.autofrage.net/nutzer/{}\",\n    \"urlMain\": \"https://www.autofrage.net/\",\n    \"username_claimed\": \"autofrage\"\n  },\n  \"Avizo\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://www.avizo.cz/\",\n    \"url\": \"https://www.avizo.cz/{}/\",\n    \"urlMain\": \"https://www.avizo.cz/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"AWS Skills Profile\": {\n    \"errorType\": \"message\",\n    \"errorMsg\": \"shareProfileAccepted\\\":false\",\n    \"url\": \"https://skillsprofile.skillbuilder.aws/user/{}/\",\n    \"urlMain\": \"https://skillsprofile.skillbuilder.aws\",\n    \"username_claimed\": \"mayank04pant\"\n  },\n  \"BOOTH\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://booth.pm/\",\n    \"regexCheck\": \"^[\\\\w@-]+?$\",\n    \"url\": \"https://{}.booth.pm/\",\n    \"urlMain\": \"https://booth.pm/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Bandcamp\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.bandcamp.com/{}\",\n    \"urlMain\": \"https://www.bandcamp.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Bazar.cz\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://www.bazar.cz/error404.aspx\",\n    \"url\": \"https://www.bazar.cz/{}/\",\n    \"urlMain\": \"https://www.bazar.cz/\",\n    \"username_claimed\": \"pianina\"\n  },\n  \"Behance\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.behance.net/{}\",\n    \"urlMain\": \"https://www.behance.net/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Bezuzyteczna\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://bezuzyteczna.pl/uzytkownicy/{}\",\n    \"urlMain\": \"https://bezuzyteczna.pl\",\n    \"username_claimed\": \"Jackson\"\n  },\n  \"BiggerPockets\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.biggerpockets.com/users/{}\",\n    \"urlMain\": \"https://www.biggerpockets.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"BioHacking\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://forum.dangerousthings.com/u/{}\",\n    \"urlMain\": \"https://forum.dangerousthings.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"BitBucket\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z0-9-_]{1,30}$\",\n    \"url\": \"https://bitbucket.org/{}/\",\n    \"urlMain\": \"https://bitbucket.org/\",\n    \"username_claimed\": \"white\"\n  },\n  \"Bitwarden Forum\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^(?![.-])[a-zA-Z0-9_.-]{3,20}$\",\n    \"url\": \"https://community.bitwarden.com/u/{}/summary\",\n    \"urlMain\": \"https://bitwarden.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Blipfoto\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.blipfoto.com/{}\",\n    \"urlMain\": \"https://www.blipfoto.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Blitz Tactics\": {\n    \"errorMsg\": \"That page doesn't exist\",\n    \"errorType\": \"message\",\n    \"url\": \"https://blitztactics.com/{}\",\n    \"urlMain\": \"https://blitztactics.com/\",\n    \"username_claimed\": \"Lance5500\"\n  },\n  \"Blogger\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z][a-zA-Z0-9_-]*$\",\n    \"url\": \"https://{}.blogspot.com\",\n    \"urlMain\": \"https://www.blogger.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Bluesky\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://bsky.app/profile/{}.bsky.social\",\n    \"urlProbe\": \"https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor={}.bsky.social\",\n    \"urlMain\": \"https://bsky.app/\",\n    \"username_claimed\": \"mcuban\"\n  },\n  \"BongaCams\": {\n    \"errorType\": \"status_code\",\n    \"isNSFW\": true,\n    \"url\": \"https://pt.bongacams.com/profile/{}\",\n    \"urlMain\": \"https://pt.bongacams.com\",\n    \"username_claimed\": \"asuna-black\"\n  },\n  \"Bookcrossing\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.bookcrossing.com/mybookshelf/{}/\",\n    \"urlMain\": \"https://www.bookcrossing.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"BoardGameGeek\": {\n    \"errorMsg\": \"\\\"isValid\\\":true\",\n    \"errorType\": \"message\",\n    \"url\": \"https://boardgamegeek.com/user/{}\",\n    \"urlMain\": \"https://boardgamegeek.com/\",\n    \"urlProbe\": \"https://api.geekdo.com/api/accounts/validate/username?username={}\",\n    \"username_claimed\": \"blue\"\n  },\n  \"BraveCommunity\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://community.brave.com/u/{}/\",\n    \"urlMain\": \"https://community.brave.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"BreachSta.rs Forum\": {\n    \"errorMsg\": \"<title>Error - BreachStars</title>\",\n    \"errorType\": \"message\",\n    \"url\": \"https://breachsta.rs/profile/{}\",\n    \"urlMain\": \"https://breachsta.rs/\",\n    \"username_claimed\": \"Sleepybubble\"\n  },\n  \"BugCrowd\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://bugcrowd.com/{}\",\n    \"urlMain\": \"https://bugcrowd.com/\",\n    \"username_claimed\": \"ppfeister\"\n  },\n  \"BuyMeACoffee\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"[a-zA-Z0-9]{3,15}\",\n    \"url\": \"https://buymeacoff.ee/{}\",\n    \"urlMain\": \"https://www.buymeacoffee.com/\",\n    \"urlProbe\": \"https://www.buymeacoffee.com/{}\",\n    \"username_claimed\": \"red\"\n  },\n  \"BuzzFeed\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://buzzfeed.com/{}\",\n    \"urlMain\": \"https://buzzfeed.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Cfx.re Forum\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://forum.cfx.re/u/{}/summary\",\n    \"urlMain\": \"https://forum.cfx.re\",\n    \"username_claimed\": \"hightowerlssd\"\n  },\n  \"CGTrader\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[^.]*?$\",\n    \"url\": \"https://www.cgtrader.com/{}\",\n    \"urlMain\": \"https://www.cgtrader.com\",\n    \"username_claimed\": \"blue\"\n  },\n  \"CNET\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-z].*$\",\n    \"url\": \"https://www.cnet.com/profiles/{}/\",\n    \"urlMain\": \"https://www.cnet.com/\",\n    \"username_claimed\": \"melliott\"\n  },\n  \"CSSBattle\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://cssbattle.dev/player/{}\",\n    \"urlMain\": \"https://cssbattle.dev\",\n    \"username_claimed\": \"beo\"\n  },\n  \"CTAN\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://ctan.org/author/{}\",\n    \"urlMain\": \"https://ctan.org/\",\n    \"username_claimed\": \"briggs\"\n  },\n  \"Caddy Community\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://caddy.community/u/{}/summary\",\n    \"urlMain\": \"https://caddy.community/\",\n    \"username_claimed\": \"taako_magnusen\"\n  },\n  \"Car Talk Community\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://community.cartalk.com/u/{}/summary\",\n    \"urlMain\": \"https://community.cartalk.com/\",\n    \"username_claimed\": \"always_fixing\"\n  },\n  \"Carbonmade\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://carbonmade.com/fourohfour?domain={}.carbonmade.com\",\n    \"regexCheck\": \"^[\\\\w@-]+?$\",\n    \"url\": \"https://{}.carbonmade.com\",\n    \"urlMain\": \"https://carbonmade.com/\",\n    \"username_claimed\": \"jenny\"\n  },\n  \"Career.habr\": {\n    \"errorMsg\": \"<h1>\\u041e\\u0448\\u0438\\u0431\\u043a\\u0430 404</h1>\",\n    \"errorType\": \"message\",\n    \"url\": \"https://career.habr.com/{}\",\n    \"urlMain\": \"https://career.habr.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"CashApp\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://cash.app/${}\",\n    \"urlMain\": \"https://cash.app\",\n    \"username_claimed\": \"hotdiggitydog\"\n  },\n  \"Championat\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.championat.com/user/{}\",\n    \"urlMain\": \"https://www.championat.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Chaos\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://chaos.social/@{}\",\n    \"urlMain\": \"https://chaos.social/\",\n    \"username_claimed\": \"ordnung\"\n  },\n  \"Chatujme.cz\": {\n    \"errorMsg\": \"Neexistujic\\u00ed profil\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[a-zA-Z][a-zA-Z1-9_-]*$\",\n    \"url\": \"https://profil.chatujme.cz/{}\",\n    \"urlMain\": \"https://chatujme.cz/\",\n    \"username_claimed\": \"david\"\n  },\n  \"ChaturBate\": {\n    \"errorType\": \"status_code\",\n    \"isNSFW\": true,\n    \"url\": \"https://chaturbate.com/{}\",\n    \"urlMain\": \"https://chaturbate.com\",\n    \"username_claimed\": \"cute18cute\"\n  },\n  \"Chess\": {\n    \"errorMsg\": \"Username is valid\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[a-z1-9]{3,25}$\",\n    \"url\": \"https://www.chess.com/member/{}\",\n    \"urlMain\": \"https://www.chess.com/\",\n    \"urlProbe\": \"https://www.chess.com/callback/user/valid?username={}\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Choice Community\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://choice.community/u/{}/summary\",\n    \"urlMain\": \"https://choice.community/\",\n    \"username_claimed\": \"gordon\"\n  },\n  \"Clapper\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://clapperapp.com/{}\",\n    \"urlMain\": \"https://clapperapp.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"CloudflareCommunity\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://community.cloudflare.com/u/{}\",\n    \"urlMain\": \"https://community.cloudflare.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Clozemaster\": {\n    \"errorMsg\": \"Oh no! Player not found.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.clozemaster.com/players/{}\",\n    \"urlMain\": \"https://www.clozemaster.com\",\n    \"username_claimed\": \"green\"\n  },\n  \"Clubhouse\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.clubhouse.com/@{}\",\n    \"urlMain\": \"https://www.clubhouse.com\",\n    \"username_claimed\": \"waniathar\"\n  },\n  \"Code Snippet Wiki\": {\n    \"errorMsg\": \"This user has not filled out their profile page yet\",\n    \"errorType\": \"message\",\n    \"url\": \"https://codesnippets.fandom.com/wiki/User:{}\",\n    \"urlMain\": \"https://codesnippets.fandom.com\",\n    \"username_claimed\": \"bob\"\n  },\n  \"Codeberg\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://codeberg.org/{}\",\n    \"urlMain\": \"https://codeberg.org/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Codecademy\": {\n    \"errorMsg\": \"This profile could not be found\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.codecademy.com/profiles/{}\",\n    \"urlMain\": \"https://www.codecademy.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Codechef\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://www.codechef.com/\",\n    \"url\": \"https://www.codechef.com/users/{}\",\n    \"urlMain\": \"https://www.codechef.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Codeforces\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://codeforces.com/profile/{}\",\n    \"urlMain\": \"https://codeforces.com/\",\n    \"urlProbe\": \"https://codeforces.com/api/user.info?handles={}\",\n    \"username_claimed\": \"tourist\"\n  },\n  \"Codepen\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://codepen.io/{}\",\n    \"urlMain\": \"https://codepen.io/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Coders Rank\": {\n    \"errorMsg\": \"not a registered member\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$\",\n    \"url\": \"https://profile.codersrank.io/user/{}/\",\n    \"urlMain\": \"https://codersrank.io/\",\n    \"username_claimed\": \"rootkit7628\"\n  },\n  \"Coderwall\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://coderwall.com/{}\",\n    \"urlMain\": \"https://coderwall.com\",\n    \"username_claimed\": \"hacker\"\n  },\n  \"CodeSandbox\": {\n    \"errorType\": \"message\",\n    \"errorMsg\": \"Could not find user with username\",\n    \"regexCheck\": \"^[a-zA-Z0-9_-]{3,30}$\",\n    \"url\": \"https://codesandbox.io/u/{}\",\n    \"urlProbe\": \"https://codesandbox.io/api/v1/users/{}\",\n    \"urlMain\": \"https://codesandbox.io\",\n    \"username_claimed\": \"icyjoseph\"\n  },\n  \"Codewars\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.codewars.com/users/{}\",\n    \"urlMain\": \"https://www.codewars.com\",\n    \"username_claimed\": \"example\"\n  },\n  \"Codolio\": {\n    \"errorType\": \"message\",\n    \"errorMsg\": \"<title>Page Not Found | Codolio</title>\",\n    \"url\": \"https://codolio.com/profile/{}\",\n    \"urlMain\": \"https://codolio.com/\",\n    \"username_claimed\": \"testuser\",\n    \"regexCheck\": \"^[a-zA-Z0-9_-]{3,30}$\"\n  },\n  \"Coinvote\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://coinvote.cc/profile/{}\",\n    \"urlMain\": \"https://coinvote.cc/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"ColourLovers\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.colourlovers.com/lover/{}\",\n    \"urlMain\": \"https://www.colourlovers.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Contently\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://contently.com\",\n    \"regexCheck\": \"^[a-zA-Z][a-zA-Z0-9_-]*$\",\n    \"url\": \"https://{}.contently.com/\",\n    \"urlMain\": \"https://contently.com/\",\n    \"username_claimed\": \"jordanteicher\"\n  },\n  \"Coroflot\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.coroflot.com/{}\",\n    \"urlMain\": \"https://coroflot.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Cplusplus\": {\n    \"errorType\": \"message\",\n    \"errorMsg\": \"<title>404 Page Not Found</title>\",\n    \"url\": \"https://cplusplus.com/user/{}\",\n    \"urlMain\": \"https://cplusplus.com\",\n    \"username_claimed\": \"mbozzi\"\n  },\n  \"Cracked\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://www.cracked.com/\",\n    \"url\": \"https://www.cracked.com/members/{}/\",\n    \"urlMain\": \"https://www.cracked.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Cracked Forum\": {\n    \"errorMsg\": \"The member you specified is either invalid or doesn't exist\",\n    \"errorType\": \"message\",\n    \"url\": \"https://cracked.sh/{}\",\n    \"urlMain\": \"https://cracked.sh/\",\n    \"username_claimed\": \"Blue\"\n  },\n  \"Crevado\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[\\\\w@-]+?$\",\n    \"url\": \"https://{}.crevado.com\",\n    \"urlMain\": \"https://crevado.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Crowdin\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z0-9._-]{2,255}$\",\n    \"url\": \"https://crowdin.com/profile/{}\",\n    \"urlMain\": \"https://crowdin.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"CryptoHack\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://cryptohack.org/\",\n    \"url\": \"https://cryptohack.org/user/{}/\",\n    \"urlMain\": \"https://cryptohack.org/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Cryptomator Forum\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://community.cryptomator.org/u/{}\",\n    \"urlMain\": \"https://community.cryptomator.org/\",\n    \"username_claimed\": \"michael\"\n  },\n  \"Cults3D\": {\n    \"errorMsg\": \"Oh dear, this page is not working!\",\n    \"errorType\": \"message\",\n    \"url\": \"https://cults3d.com/en/users/{}/creations\",\n    \"urlMain\": \"https://cults3d.com/en\",\n    \"username_claimed\": \"brown\"\n  },\n  \"CyberDefenders\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[^\\\\/:*?\\\"<>|@]{3,50}$\",\n    \"request_method\": \"GET\",\n    \"url\": \"https://cyberdefenders.org/p/{}\",\n    \"urlMain\": \"https://cyberdefenders.org/\",\n    \"username_claimed\": \"mlohn\"\n  },\n  \"DEV Community\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z][a-zA-Z0-9_-]*$\",\n    \"url\": \"https://dev.to/{}\",\n    \"urlMain\": \"https://dev.to/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"DMOJ\": {\n    \"errorMsg\": \"No such user\",\n    \"errorType\": \"message\",\n    \"url\": \"https://dmoj.ca/user/{}\",\n    \"urlMain\": \"https://dmoj.ca/\",\n    \"username_claimed\": \"junferno\"\n  },\n  \"DailyMotion\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.dailymotion.com/{}\",\n    \"urlMain\": \"https://www.dailymotion.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"dcinside\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://gallog.dcinside.com/{}\",\n    \"urlMain\": \"https://www.dcinside.com/\",\n    \"username_claimed\": \"anrbrb\"\n  },\n  \"Dealabs\": {\n    \"errorMsg\": \"La page que vous essayez\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"[a-z0-9]{4,16}\",\n    \"url\": \"https://www.dealabs.com/profile/{}\",\n    \"urlMain\": \"https://www.dealabs.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"DeviantArt\": {\n    \"errorType\": \"message\",\n    \"errorMsg\": \"Llama Not Found\",\n    \"regexCheck\": \"^[a-zA-Z][a-zA-Z0-9_-]*$\",\n    \"url\": \"https://www.deviantart.com/{}\",\n    \"urlMain\": \"https://www.deviantart.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"DigitalSpy\": {\n    \"errorMsg\": \"The page you were looking for could not be found.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://forums.digitalspy.com/profile/{}\",\n    \"urlMain\": \"https://forums.digitalspy.com/\",\n    \"username_claimed\": \"blue\",\n    \"regexCheck\": \"^\\\\w{3,20}$\"\n  },\n  \"Discogs\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.discogs.com/user/{}\",\n    \"urlMain\": \"https://www.discogs.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Discord\": {\n    \"errorType\": \"message\",\n    \"url\": \"https://discord.com\",\n    \"urlMain\": \"https://discord.com/\",\n    \"urlProbe\": \"https://discord.com/api/v9/unique-username/username-attempt-unauthed\",\n    \"errorMsg\": [\"{\\\"taken\\\":false}\", \"The resource is being rate limited\"],\n    \"request_method\": \"POST\",\n    \"request_payload\": {\n      \"username\": \"{}\"\n    },\n    \"headers\": {\n      \"Content-Type\": \"application/json\"\n    },\n    \"username_claimed\": \"blue\"\n  },\n  \"Discord.bio\": {\n    \"errorType\": \"message\",\n    \"errorMsg\": \"<title>Server Error (500)</title>\",\n    \"url\": \"https://discords.com/api-v2/bio/details/{}\",\n    \"urlMain\": \"https://discord.bio/\",\n    \"username_claimed\": \"robert\"\n  },\n  \"Discuss.Elastic.co\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://discuss.elastic.co/u/{}\",\n    \"urlMain\": \"https://discuss.elastic.co/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Diskusjon.no\": {\n    \"errorMsg\": \"{\\\"result\\\":\\\"ok\\\"}\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[a-zA-Z0-9_.-]{3,40}$\",\n    \"urlProbe\": \"https://www.diskusjon.no/?app=core&module=system&controller=ajax&do=usernameExists&input={}\",\n    \"url\": \"https://www.diskusjon.no\",\n    \"urlMain\": \"https://www.diskusjon.no\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Disqus\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://disqus.com/{}\",\n    \"urlMain\": \"https://disqus.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Docker Hub\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://hub.docker.com/u/{}/\",\n    \"urlMain\": \"https://hub.docker.com/\",\n    \"urlProbe\": \"https://hub.docker.com/v2/users/{}/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Dribbble\": {\n    \"errorMsg\": \"Whoops, that page is gone.\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[a-zA-Z][a-zA-Z0-9_-]*$\",\n    \"url\": \"https://dribbble.com/{}\",\n    \"urlMain\": \"https://dribbble.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Duolingo\": {\n    \"errorMsg\": \"{\\\"users\\\":[]}\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.duolingo.com/profile/{}\",\n    \"urlMain\": \"https://duolingo.com/\",\n    \"urlProbe\": \"https://www.duolingo.com/2017-06-30/users?username={}\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Eintracht Frankfurt Forum\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[^.]*?$\",\n    \"url\": \"https://community.eintracht.de/fans/{}\",\n    \"urlMain\": \"https://community.eintracht.de/\",\n    \"username_claimed\": \"mmammu\"\n  },\n  \"Empretienda AR\": {\n    \"__comment__\": \"Note that Error Connecting responses may be indicative of unclaimed handles\",\n    \"errorType\": \"status_code\",\n    \"url\": \"https://{}.empretienda.com.ar\",\n    \"urlMain\": \"https://empretienda.com\",\n    \"username_claimed\": \"camalote\"\n  },\n  \"Envato Forum\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://forums.envato.com/u/{}\",\n    \"urlMain\": \"https://forums.envato.com/\",\n    \"username_claimed\": \"enabled\"\n  },\n  \"Erome\": {\n    \"errorType\": \"status_code\",\n    \"isNSFW\": true,\n    \"url\": \"https://www.erome.com/{}\",\n    \"urlMain\": \"https://www.erome.com/\",\n    \"username_claimed\": \"bob\"\n  },\n  \"Exposure\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z0-9-]{1,63}$\",\n    \"url\": \"https://{}.exposure.co/\",\n    \"urlMain\": \"https://exposure.co/\",\n    \"username_claimed\": \"jonasjacobsson\"\n  },\n  \"exophase\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.exophase.com/user/{}/\",\n    \"urlMain\": \"https://www.exophase.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"EyeEm\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.eyeem.com/u/{}\",\n    \"urlMain\": \"https://www.eyeem.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"F3.cool\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://f3.cool/{}/\",\n    \"urlMain\": \"https://f3.cool/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Fameswap\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://fameswap.com/user/{}\",\n    \"urlMain\": \"https://fameswap.com/\",\n    \"username_claimed\": \"fameswap\"\n  },\n  \"Fandom\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.fandom.com/u/{}\",\n    \"urlMain\": \"https://www.fandom.com/\",\n    \"username_claimed\": \"Jungypoo\"\n  },\n  \"Fanpop\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://www.fanpop.com/\",\n    \"url\": \"https://www.fanpop.com/fans/{}\",\n    \"urlMain\": \"https://www.fanpop.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Finanzfrage\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.finanzfrage.net/nutzer/{}\",\n    \"urlMain\": \"https://www.finanzfrage.net/\",\n    \"username_claimed\": \"finanzfrage\"\n  },\n  \"Flickr\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.flickr.com/people/{}\",\n    \"urlMain\": \"https://www.flickr.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Flightradar24\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z0-9_]{3,20}$\",\n    \"url\": \"https://my.flightradar24.com/{}\",\n    \"urlMain\": \"https://www.flightradar24.com/\",\n    \"username_claimed\": \"jebbrooks\"\n  },\n  \"Flipboard\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^([a-zA-Z0-9_]){1,15}$\",\n    \"url\": \"https://flipboard.com/@{}\",\n    \"urlMain\": \"https://flipboard.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Football\": {\n    \"errorMsg\": \"\\u041f\\u043e\\u043b\\u044c\\u0437\\u043e\\u0432\\u0430\\u0442\\u0435\\u043b\\u044c \\u0441 \\u0442\\u0430\\u043a\\u0438\\u043c \\u0438\\u043c\\u0435\\u043d\\u0435\\u043c \\u043d\\u0435 \\u043d\\u0430\\u0439\\u0434\\u0435\\u043d\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.rusfootball.info/user/{}/\",\n    \"urlMain\": \"https://www.rusfootball.info/\",\n    \"username_claimed\": \"solo87\"\n  },\n  \"FortniteTracker\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://fortnitetracker.com/profile/all/{}\",\n    \"urlMain\": \"https://fortnitetracker.com/challenges\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Forum Ophilia\": {\n    \"errorMsg\": \"that user does not exist\",\n    \"errorType\": \"message\",\n    \"isNSFW\": true,\n    \"url\": \"https://www.forumophilia.com/profile.php?mode=viewprofile&u={}\",\n    \"urlMain\": \"https://www.forumophilia.com/\",\n    \"username_claimed\": \"bob\"\n  },\n  \"Fosstodon\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z0-9_]{1,30}$\",\n    \"url\": \"https://fosstodon.org/@{}\",\n    \"urlMain\": \"https://fosstodon.org/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Framapiaf\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z0-9_]{1,30}$\",\n    \"url\": \"https://framapiaf.org/@{}\",\n    \"urlMain\": \"https://framapiaf.org\",\n    \"username_claimed\": \"pylapp\"\n  },\n  \"Freelancer\": {\n    \"errorMsg\": \"\\\"users\\\":{}\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.freelancer.com/u/{}\",\n    \"urlMain\": \"https://www.freelancer.com/\",\n    \"urlProbe\": \"https://www.freelancer.com/api/users/0.1/users?usernames%5B%5D={}&compact=true\",\n    \"username_claimed\": \"red0xff\"\n  },\n  \"Freesound\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://freesound.org/people/{}/\",\n    \"urlMain\": \"https://freesound.org/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"GNOME VCS\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://gitlab.gnome.org/{}\",\n    \"regexCheck\": \"^(?!-)[a-zA-Z0-9_.-]{2,255}(?<!\\\\.)$\",\n    \"url\": \"https://gitlab.gnome.org/{}\",\n    \"urlMain\": \"https://gitlab.gnome.org/\",\n    \"username_claimed\": \"adam\"\n  },\n  \"GaiaOnline\": {\n    \"errorMsg\": \"No user ID specified or user does not exist\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.gaiaonline.com/profiles/{}\",\n    \"urlMain\": \"https://www.gaiaonline.com/\",\n    \"username_claimed\": \"adam\"\n  },\n  \"Gamespot\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.gamespot.com/profile/{}/\",\n    \"urlMain\": \"https://www.gamespot.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"GameFAQs\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://gamefaqs.gamespot.com/community/{}\",\n    \"urlMain\": \"https://gamefaqs.gamespot.com\",\n    \"username_claimed\": \"blue\"\n  },\n  \"GeeksforGeeks\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://auth.geeksforgeeks.org/user/{}\",\n    \"urlMain\": \"https://www.geeksforgeeks.org/\",\n    \"username_claimed\": \"adam\"\n  },\n  \"Genius (Artists)\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z0-9]{5,50}$\",\n    \"url\": \"https://genius.com/artists/{}\",\n    \"urlMain\": \"https://genius.com/\",\n    \"username_claimed\": \"genius\"\n  },\n  \"Genius (Users)\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z0-9]*?$\",\n    \"url\": \"https://genius.com/{}\",\n    \"urlMain\": \"https://genius.com/\",\n    \"username_claimed\": \"genius\"\n  },\n  \"Gesundheitsfrage\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.gesundheitsfrage.net/nutzer/{}\",\n    \"urlMain\": \"https://www.gesundheitsfrage.net/\",\n    \"username_claimed\": \"gutefrage\"\n  },\n  \"GetMyUni\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.getmyuni.com/user/{}\",\n    \"urlMain\": \"https://getmyuni.com/\",\n    \"username_claimed\": \"Upneet.Grover17\"\n  },\n  \"Giant Bomb\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.giantbomb.com/profile/{}/\",\n    \"urlMain\": \"https://www.giantbomb.com/\",\n    \"username_claimed\": \"bob\"\n  },\n  \"Giphy\": {\n    \"errorType\": \"message\",\n    \"errorMsg\": \"<title> GIFs - Find &amp; Share on GIPHY</title>\",\n    \"url\": \"https://giphy.com/{}\",\n    \"urlMain\": \"https://giphy.com/\",\n    \"username_claimed\": \"red\"\n  },\n  \"GitBook\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[\\\\w@-]+?$\",\n    \"url\": \"https://{}.gitbook.io/\",\n    \"urlMain\": \"https://gitbook.com/\",\n    \"username_claimed\": \"gitbook\"\n  },\n  \"GitHub\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$\",\n    \"url\": \"https://www.github.com/{}\",\n    \"urlMain\": \"https://www.github.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Warframe Market\": {\n    \"errorType\": \"status_code\",\n    \"request_method\": \"GET\",\n    \"url\": \"https://warframe.market/profile/{}\",\n    \"urlMain\": \"https://warframe.market/\",\n    \"urlProbe\": \"https://api.warframe.market/v2/user/{}\",\n    \"username_claimed\": \"kaiallalone\"\n  },\n  \"GitLab\": {\n    \"errorMsg\": \"[]\",\n    \"errorType\": \"message\",\n    \"url\": \"https://gitlab.com/{}\",\n    \"urlMain\": \"https://gitlab.com/\",\n    \"urlProbe\": \"https://gitlab.com/api/v4/users?username={}\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Gitea\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://gitea.com/{}\",\n    \"urlMain\": \"https://gitea.com/\",\n    \"username_claimed\": \"xorm\"\n  },\n  \"Gitee\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://gitee.com/{}\",\n    \"urlMain\": \"https://gitee.com/\",\n    \"username_claimed\": \"wizzer\"\n  },\n  \"GoodReads\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.goodreads.com/{}\",\n    \"urlMain\": \"https://www.goodreads.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Google Play\": {\n    \"errorMsg\": \"the requested URL was not found on this server\",\n    \"errorType\": \"message\",\n    \"url\": \"https://play.google.com/store/apps/developer?id={}\",\n    \"urlMain\": \"https://play.google.com\",\n    \"username_claimed\": \"GitHub\"\n  },\n  \"Gradle\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^(?!-)[a-zA-Z0-9-]{3,}(?<!-)$\",\n    \"url\": \"https://plugins.gradle.org/u/{}\",\n    \"urlMain\": \"https://gradle.org/\",\n    \"username_claimed\": \"jetbrains\"\n  },\n  \"Grailed\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://www.grailed.com/{}\",\n    \"url\": \"https://www.grailed.com/{}\",\n    \"urlMain\": \"https://www.grailed.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Gravatar\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^((?!\\\\.).)*$\",\n    \"url\": \"http://en.gravatar.com/{}\",\n    \"urlMain\": \"http://en.gravatar.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Gumroad\": {\n    \"errorMsg\": \"Page not found (404) - Gumroad\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[^.]*?$\",\n    \"url\": \"https://www.gumroad.com/{}\",\n    \"urlMain\": \"https://www.gumroad.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Gutefrage\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.gutefrage.net/nutzer/{}\",\n    \"urlMain\": \"https://www.gutefrage.net/\",\n    \"username_claimed\": \"gutefrage\"\n  },\n  \"HackTheBox\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://forum.hackthebox.com/u/{}\",\n    \"urlMain\": \"https://forum.hackthebox.com/\",\n    \"username_claimed\": \"angar\"\n  },\n  \"Hackaday\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://hackaday.io/{}\",\n    \"urlMain\": \"https://hackaday.io/\",\n    \"username_claimed\": \"adam\"\n  },\n  \"HackenProof (Hackers)\": {\n    \"errorMsg\": \"Page not found\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[\\\\w-]{,34}$\",\n    \"url\": \"https://hackenproof.com/hackers/{}\",\n    \"urlMain\": \"https://hackenproof.com/\",\n    \"username_claimed\": \"blazezaria\"\n  },\n  \"HackerEarth\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://hackerearth.com/@{}\",\n    \"urlMain\": \"https://hackerearth.com/\",\n    \"username_claimed\": \"naveennamani877\"\n  },\n  \"HackerNews\": {\n    \"__comment__\": \"First errMsg invalid, second errMsg rate limited. Not ideal. Adjust for better rate limit filtering.\",\n    \"errorMsg\": [\"No such user.\", \"Sorry.\"],\n    \"errorType\": \"message\",\n    \"url\": \"https://news.ycombinator.com/user?id={}\",\n    \"urlMain\": \"https://news.ycombinator.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"HackerOne\": {\n    \"errorMsg\": \"Page not found\",\n    \"errorType\": \"message\",\n    \"url\": \"https://hackerone.com/{}\",\n    \"urlMain\": \"https://hackerone.com/\",\n    \"username_claimed\": \"stok\"\n  },\n  \"HackerRank\": {\n    \"errorMsg\": \"Something went wrong\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[^.]*?$\",\n    \"url\": \"https://hackerrank.com/{}\",\n    \"urlMain\": \"https://hackerrank.com/\",\n    \"username_claimed\": \"satznova\"\n  },\n  \"HackerSploit\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://forum.hackersploit.org/u/{}\",\n    \"urlMain\": \"https://forum.hackersploit.org/\",\n    \"username_claimed\": \"hackersploit\"\n  },\n  \"HackMD\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://hackmd.io/@{}\",\n    \"urlMain\": \"https://hackmd.io/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Harvard Scholar\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://scholar.harvard.edu/{}\",\n    \"urlMain\": \"https://scholar.harvard.edu/\",\n    \"username_claimed\": \"ousmanekane\"\n  },\n  \"Hashnode\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://hashnode.com/@{}\",\n    \"urlMain\": \"https://hashnode.com\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Heavy-R\": {\n    \"errorMsg\": \"Channel not found\",\n    \"errorType\": \"message\",\n    \"isNSFW\": true,\n    \"url\": \"https://www.heavy-r.com/user/{}\",\n    \"urlMain\": \"https://www.heavy-r.com/\",\n    \"username_claimed\": \"kilroy222\"\n  },\n  \"Hive Blog\": {\n    \"errorMsg\": \"<title>User Not Found - Hive</title>\",\n    \"errorType\": \"message\",\n    \"url\": \"https://hive.blog/@{}\",\n    \"urlMain\": \"https://hive.blog/\",\n    \"username_claimed\": \"mango-juice\"\n  },\n  \"Holopin\": {\n    \"errorMsg\": \"true\",\n    \"errorType\": \"message\",\n    \"request_method\": \"POST\",\n    \"request_payload\": {\n      \"username\": \"{}\"\n    },\n    \"url\": \"https://holopin.io/@{}\",\n    \"urlMain\": \"https://holopin.io\",\n    \"urlProbe\": \"https://www.holopin.io/api/auth/username\",\n    \"username_claimed\": \"red\"\n  },\n  \"Houzz\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://houzz.com/user/{}\",\n    \"urlMain\": \"https://houzz.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"HubPages\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://hubpages.com/@{}\",\n    \"urlMain\": \"https://hubpages.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Hubski\": {\n    \"errorMsg\": \"No such user\",\n    \"errorType\": \"message\",\n    \"url\": \"https://hubski.com/user/{}\",\n    \"urlMain\": \"https://hubski.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"HudsonRock\": {\n    \"errorMsg\": \"This username is not associated\",\n    \"errorType\": \"message\",\n    \"url\": \"https://cavalier.hudsonrock.com/api/json/v2/osint-tools/search-by-username?username={}\",\n    \"urlMain\": \"https://hudsonrock.com\",\n    \"username_claimed\": \"testadmin\"\n  },\n  \"Hugging Face\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://huggingface.co/{}\",\n    \"urlMain\": \"https://huggingface.co/\",\n    \"username_claimed\": \"Pasanlaksitha\"\n  },\n  \"IFTTT\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[A-Za-z0-9]{3,35}$\",\n    \"url\": \"https://www.ifttt.com/p/{}\",\n    \"urlMain\": \"https://www.ifttt.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Ifunny\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://ifunny.co/user/{}\",\n    \"urlMain\": \"https://ifunny.co/\",\n    \"username_claimed\": \"agua\"\n  },\n  \"IRC-Galleria\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://irc-galleria.net/users/search?username={}\",\n    \"url\": \"https://irc-galleria.net/user/{}\",\n    \"urlMain\": \"https://irc-galleria.net/\",\n    \"username_claimed\": \"appas\"\n  },\n  \"Icons8 Community\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://community.icons8.com/u/{}/summary\",\n    \"urlMain\": \"https://community.icons8.com/\",\n    \"username_claimed\": \"thefourCraft\"\n  },\n  \"Image Fap\": {\n    \"errorMsg\": \"Not found\",\n    \"errorType\": \"message\",\n    \"isNSFW\": true,\n    \"url\": \"https://www.imagefap.com/profile/{}\",\n    \"urlMain\": \"https://www.imagefap.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"ImgUp.cz\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://imgup.cz/{}\",\n    \"urlMain\": \"https://imgup.cz/\",\n    \"username_claimed\": \"adam\"\n  },\n  \"Imgur\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://imgur.com/user/{}\",\n    \"urlMain\": \"https://imgur.com/\",\n    \"urlProbe\": \"https://api.imgur.com/account/v1/accounts/{}?client_id=546c25a59c58ad7\",\n    \"username_claimed\": \"blue\"\n  },\n  \"imood\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.imood.com/users/{}\",\n    \"urlMain\": \"https://www.imood.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Instagram\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://instagram.com/{}\",\n    \"urlMain\": \"https://instagram.com/\",\n    \"urlProbe\": \"https://imginn.com/{}\",\n    \"username_claimed\": \"instagram\"\n  },\n  \"Instapaper\": {\n    \"errorType\": \"status_code\",\n    \"request_method\": \"GET\",\n    \"url\": \"https://www.instapaper.com/p/{}\",\n    \"urlMain\": \"https://www.instapaper.com/\",\n    \"username_claimed\": \"john\"\n  },\n  \"Instructables\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.instructables.com/member/{}\",\n    \"urlMain\": \"https://www.instructables.com/\",\n    \"urlProbe\": \"https://www.instructables.com/json-api/showAuthorExists?screenName={}\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Intigriti\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"[a-z0-9_]{1,25}\",\n    \"request_method\": \"GET\",\n    \"url\": \"https://app.intigriti.com/profile/{}\",\n    \"urlMain\": \"https://app.intigriti.com\",\n    \"urlProbe\": \"https://api.intigriti.com/user/public/profile/{}\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Ionic Forum\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://forum.ionicframework.com/u/{}\",\n    \"urlMain\": \"https://forum.ionicframework.com/\",\n    \"username_claimed\": \"theblue222\"\n  },\n  \"Issuu\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://issuu.com/{}\",\n    \"urlMain\": \"https://issuu.com/\",\n    \"username_claimed\": \"jenny\"\n  },\n  \"Itch.io\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[\\\\w@-]+?$\",\n    \"url\": \"https://{}.itch.io/\",\n    \"urlMain\": \"https://itch.io/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Itemfix\": {\n    \"errorMsg\": \"<title>ItemFix - Channel: </title>\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.itemfix.com/c/{}\",\n    \"urlMain\": \"https://www.itemfix.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Jellyfin Weblate\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z0-9@._-]{1,150}$\",\n    \"url\": \"https://translate.jellyfin.org/user/{}/\",\n    \"urlMain\": \"https://translate.jellyfin.org/\",\n    \"username_claimed\": \"EraYaN\"\n  },\n  \"Jimdo\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[\\\\w@-]+?$\",\n    \"url\": \"https://{}.jimdosite.com\",\n    \"urlMain\": \"https://jimdosite.com/\",\n    \"username_claimed\": \"jenny\"\n  },\n  \"Joplin Forum\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://discourse.joplinapp.org/u/{}\",\n    \"urlMain\": \"https://discourse.joplinapp.org/\",\n    \"username_claimed\": \"laurent\"\n  },\n  \"Jupyter Community Forum\": {\n    \"errorMsg\": \"Oops! That page doesn’t exist or is private.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://discourse.jupyter.org/u/{}/summary\",\n    \"urlMain\": \"https://discourse.jupyter.org\",\n    \"username_claimed\": \"choldgraf\"\n  },\n  \"Kaggle\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.kaggle.com/{}\",\n    \"urlMain\": \"https://www.kaggle.com/\",\n    \"username_claimed\": \"dansbecker\"\n  },\n  \"kaskus\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.kaskus.co.id/@{}\",\n    \"urlMain\": \"https://www.kaskus.co.id\",\n    \"urlProbe\": \"https://www.kaskus.co.id/api/users?username={}\",\n    \"request_method\": \"GET\",\n    \"username_claimed\": \"l0mbart\"\n  },\n  \"Keybase\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://keybase.io/{}\",\n    \"urlMain\": \"https://keybase.io/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Kick\": {\n    \"__comment__\": \"Cloudflare. Only viable when proxied.\",\n    \"errorType\": \"status_code\",\n    \"url\": \"https://kick.com/{}\",\n    \"urlMain\": \"https://kick.com/\",\n    \"urlProbe\": \"https://kick.com/api/v2/channels/{}\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Kik\": {\n    \"errorMsg\": \"The page you requested was not found\",\n    \"errorType\": \"message\",\n    \"url\": \"https://kik.me/{}\",\n    \"urlMain\": \"http://kik.me/\",\n    \"urlProbe\": \"https://ws2.kik.com/user/{}\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Kongregate\": {\n    \"errorType\": \"status_code\",\n    \"headers\": {\n      \"Accept\": \"text/html\"\n    },\n    \"regexCheck\": \"^[a-zA-Z][a-zA-Z0-9_-]*$\",\n    \"url\": \"https://www.kongregate.com/accounts/{}\",\n    \"urlMain\": \"https://www.kongregate.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Kvinneguiden\": {\n    \"errorMsg\": \"{\\\"result\\\":\\\"ok\\\"}\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[a-zA-Z0-9_.-]{3,18}$\",\n    \"urlProbe\": \"https://forum.kvinneguiden.no/?app=core&module=system&controller=ajax&do=usernameExists&input={}\",\n    \"url\": \"https://forum.kvinneguiden.no\",\n    \"urlMain\": \"https://forum.kvinneguiden.no\",\n    \"username_claimed\": \"blue\"\n  },\n  \"LOR\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.linux.org.ru/people/{}/profile\",\n    \"urlMain\": \"https://linux.org.ru/\",\n    \"username_claimed\": \"red\"\n  },\n  \"Laracast\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://laracasts.com/@{}\",\n    \"urlMain\": \"https://laracasts.com/\",\n    \"regexCheck\": \"^[a-zA-Z0-9_-]{3,}$\",\n    \"username_claimed\": \"user1\"\n  },\n  \"Launchpad\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://launchpad.net/~{}\",\n    \"urlMain\": \"https://launchpad.net/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"LeetCode\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://leetcode.com/{}\",\n    \"urlMain\": \"https://leetcode.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"LemmyWorld\": {\n    \"errorType\": \"message\",\n    \"errorMsg\": \"<h1>Error!</h1>\",\n    \"url\": \"https://lemmy.world/u/{}\",\n    \"urlMain\": \"https://lemmy.world\",\n    \"username_claimed\": \"blue\"\n  },\n  \"LessWrong\": {\n    \"url\": \"https://www.lesswrong.com/users/{}\",\n    \"urlMain\": \"https://www.lesswrong.com/\",\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://www.lesswrong.com/\",\n    \"username_claimed\": \"habryka\"\n  },\n  \"Letterboxd\": {\n    \"errorMsg\": \"Sorry, we can\\u2019t find the page you\\u2019ve requested.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://letterboxd.com/{}\",\n    \"urlMain\": \"https://letterboxd.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"LibraryThing\": {\n    \"errorMsg\": \"<p>Error: This user doesn't exist</p>\",\n    \"errorType\": \"message\",\n    \"headers\": {\n      \"Cookie\": \"LTAnonSessionID=3159599315; LTUnifiedCookie=%7B%22areyouhuman%22%3A1%7D; \"\n    },\n    \"url\": \"https://www.librarything.com/profile/{}\",\n    \"urlMain\": \"https://www.librarything.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Lichess\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://lichess.org/@/{}\",\n    \"urlMain\": \"https://lichess.org\",\n    \"username_claimed\": \"john\"\n  },\n  \"LinkedIn\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z0-9]{3,100}$\",\n    \"request_method\": \"GET\",\n    \"url\": \"https://linkedin.com/in/{}\",\n    \"urlMain\": \"https://linkedin.com\",\n    \"username_claimed\": \"paulpfeister\"\n  },\n  \"Linktree\": {\n    \"errorMsg\": \"\\\"statusCode\\\":404\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[\\\\w\\\\.]{2,30}$\",\n    \"url\": \"https://linktr.ee/{}\",\n    \"urlMain\": \"https://linktr.ee/\",\n    \"username_claimed\": \"anne\"\n  },\n  \"LinuxFR.org\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://linuxfr.org/users/{}\",\n    \"urlMain\": \"https://linuxfr.org/\",\n    \"username_claimed\": \"pylapp\"\n  },\n  \"Listed\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://listed.to/@{}\",\n    \"url\": \"https://listed.to/@{}\",\n    \"urlMain\": \"https://listed.to/\",\n    \"username_claimed\": \"listed\"\n  },\n  \"LiveJournal\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z][a-zA-Z0-9_-]*$\",\n    \"url\": \"https://{}.livejournal.com\",\n    \"urlMain\": \"https://www.livejournal.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Lobsters\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"[A-Za-z0-9][A-Za-z0-9_-]{0,24}\",\n    \"url\": \"https://lobste.rs/u/{}\",\n    \"urlMain\": \"https://lobste.rs/\",\n    \"username_claimed\": \"jcs\"\n  },\n  \"LottieFiles\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://lottiefiles.com/{}\",\n    \"urlMain\": \"https://lottiefiles.com/\",\n    \"username_claimed\": \"lottiefiles\"\n  },\n  \"LushStories\": {\n    \"errorType\": \"status_code\",\n    \"isNSFW\": true,\n    \"url\": \"https://www.lushstories.com/profile/{}\",\n    \"urlMain\": \"https://www.lushstories.com/\",\n    \"username_claimed\": \"chris_brown\"\n  },\n  \"MMORPG Forum\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://forums.mmorpg.com/profile/{}\",\n    \"urlMain\": \"https://forums.mmorpg.com/\",\n    \"username_claimed\": \"goku\"\n  },\n  \"Mamot\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z0-9_]{1,30}$\",\n    \"url\": \"https://mamot.fr/@{}\",\n    \"urlMain\": \"https://mamot.fr/\",\n    \"username_claimed\": \"anciensEnssat\"\n  },\n  \"Medium\": {\n    \"errorMsg\": \"<body\",\n    \"errorType\": \"message\",\n    \"url\": \"https://medium.com/@{}\",\n    \"urlMain\": \"https://medium.com/\",\n    \"urlProbe\": \"https://medium.com/feed/@{}\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Memrise\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.memrise.com/user/{}/\",\n    \"urlMain\": \"https://www.memrise.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Minecraft\": {\n    \"errorMsg\": \"Couldn't find any profile with name\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^.{1,25}$\",\n    \"url\": \"https://api.mojang.com/users/profiles/minecraft/{}\",\n    \"urlMain\": \"https://minecraft.net/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"MixCloud\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.mixcloud.com/{}/\",\n    \"urlMain\": \"https://www.mixcloud.com/\",\n    \"urlProbe\": \"https://api.mixcloud.com/{}/\",\n    \"username_claimed\": \"jenny\"\n  },\n  \"Monkeytype\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://monkeytype.com/profile/{}\",\n    \"urlMain\": \"https://monkeytype.com/\",\n    \"urlProbe\": \"https://api.monkeytype.com/users/{}/profile\",\n    \"username_claimed\": \"Lost_Arrow\"\n  },\n  \"Motherless\": {\n    \"errorMsg\": \"no longer a member\",\n    \"errorType\": \"message\",\n    \"isNSFW\": true,\n    \"url\": \"https://motherless.com/m/{}\",\n    \"urlMain\": \"https://motherless.com/\",\n    \"username_claimed\": \"hacker\"\n  },\n  \"Motorradfrage\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.motorradfrage.net/nutzer/{}\",\n    \"urlMain\": \"https://www.motorradfrage.net/\",\n    \"username_claimed\": \"gutefrage\"\n  },\n  \"MuseScore\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://musescore.com/{}\",\n    \"urlMain\": \"https://musescore.com/\",\n    \"username_claimed\": \"arrangeme\",\n    \"request_method\": \"GET\"\n  },\n  \"MyAnimeList\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://myanimelist.net/profile/{}\",\n    \"urlMain\": \"https://myanimelist.net/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"MyMiniFactory\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.myminifactory.com/users/{}\",\n    \"urlMain\": \"https://www.myminifactory.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Mydramalist\": {\n    \"errorMsg\": \"The requested page was not found\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.mydramalist.com/profile/{}\",\n    \"urlMain\": \"https://mydramalist.com\",\n    \"username_claimed\": \"elhadidy12398\"\n  },\n  \"Myspace\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://myspace.com/{}\",\n    \"urlMain\": \"https://myspace.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"NICommunityForum\": {\n    \"errorMsg\": \"The page you were looking for could not be found.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://community.native-instruments.com/profile/{}\",\n    \"urlMain\": \"https://www.native-instruments.com/forum/\",\n    \"username_claimed\": \"jambert\"\n  },\n  \"namuwiki\": {\n    \"__comment__\": \"This is a Korean site and it's expected to return false negatives in certain other regions.\",\n    \"errorType\": \"status_code\",\n    \"url\": \"https://namu.wiki/w/%EC%82%AC%EC%9A%A9%EC%9E%90:{}\",\n    \"urlMain\": \"https://namu.wiki/\",\n    \"username_claimed\": \"namu\"\n  },\n  \"NationStates Nation\": {\n    \"errorMsg\": \"Was this your nation? It may have ceased to exist due to inactivity, but can rise again!\",\n    \"errorType\": \"message\",\n    \"url\": \"https://nationstates.net/nation={}\",\n    \"urlMain\": \"https://nationstates.net\",\n    \"username_claimed\": \"the_holy_principality_of_saint_mark\"\n  },\n  \"NationStates Region\": {\n    \"errorMsg\": \"does not exist.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://nationstates.net/region={}\",\n    \"urlMain\": \"https://nationstates.net\",\n    \"username_claimed\": \"the_west_pacific\"\n  },\n  \"Naver\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://blog.naver.com/{}\",\n    \"urlMain\": \"https://naver.com\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Needrom\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.needrom.com/author/{}/\",\n    \"urlMain\": \"https://www.needrom.com/\",\n    \"username_claimed\": \"needrom\"\n  },\n  \"Newgrounds\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z][a-zA-Z0-9_-]*$\",\n    \"url\": \"https://{}.newgrounds.com\",\n    \"urlMain\": \"https://newgrounds.com\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Nextcloud Forum\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^(?![.-])[a-zA-Z0-9_.-]{3,20}$\",\n    \"url\": \"https://help.nextcloud.com/u/{}/summary\",\n    \"urlMain\": \"https://nextcloud.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Nightbot\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://nightbot.tv/t/{}/commands\",\n    \"urlMain\": \"https://nightbot.tv/\",\n    \"urlProbe\": \"https://api.nightbot.tv/1/channels/t/{}\",\n    \"username_claimed\": \"green\"\n  },\n  \"Ninja Kiwi\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://ninjakiwi.com/profile/{}\",\n    \"url\": \"https://ninjakiwi.com/profile/{}\",\n    \"urlMain\": \"https://ninjakiwi.com/\",\n    \"username_claimed\": \"Kyruko\"\n  },\n  \"NintendoLife\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.nintendolife.com/users/{}\",\n    \"urlMain\": \"https://www.nintendolife.com/\",\n    \"username_claimed\": \"goku\"\n  },\n  \"NitroType\": {\n    \"errorMsg\": \"<title>Nitro Type | Competitive Typing Game | Race Your Friends</title>\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.nitrotype.com/racer/{}\",\n    \"urlMain\": \"https://www.nitrotype.com/\",\n    \"username_claimed\": \"jianclash\"\n  },\n  \"NotABug.org\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://notabug.org/{}\",\n    \"urlMain\": \"https://notabug.org/\",\n    \"urlProbe\": \"https://notabug.org/{}/followers\",\n    \"username_claimed\": \"red\"\n  },\n  \"Nothing Community\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://nothing.community/u/{}\",\n    \"urlMain\": \"https://nothing.community/\",\n    \"username_claimed\": \"Carl\"\n  },\n  \"Nyaa.si\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://nyaa.si/user/{}\",\n    \"urlMain\": \"https://nyaa.si/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"ObservableHQ\": {\n    \"errorType\": \"message\",\n    \"errorMsg\": \"Page not found\",\n    \"url\": \"https://observablehq.com/@{}\",\n    \"urlMain\": \"https://observablehq.com/\",\n    \"username_claimed\": \"mbostock\"\n  },\n  \"Open Collective\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://opencollective.com/{}\",\n    \"urlMain\": \"https://opencollective.com/\",\n    \"username_claimed\": \"sindresorhus\"\n  },\n  \"OpenGameArt\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://opengameart.org/users/{}\",\n    \"urlMain\": \"https://opengameart.org\",\n    \"username_claimed\": \"ski\"\n  },\n  \"OpenStreetMap\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[^.]*?$\",\n    \"url\": \"https://www.openstreetmap.org/user/{}\",\n    \"urlMain\": \"https://www.openstreetmap.org/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Odysee\": {\n    \"errorMsg\": \"<link rel=\\\"canonical\\\" content=\\\"odysee.com\\\"/>\",\n    \"errorType\": \"message\",\n    \"url\": \"https://odysee.com/@{}\",\n    \"urlMain\": \"https://odysee.com/\",\n    \"username_claimed\": \"Odysee\"\n  },\n  \"Opensource\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://opensource.com/users/{}\",\n    \"urlMain\": \"https://opensource.com/\",\n    \"username_claimed\": \"red\"\n  },\n  \"OurDJTalk\": {\n    \"errorMsg\": \"The specified member cannot be found\",\n    \"errorType\": \"message\",\n    \"url\": \"https://ourdjtalk.com/members?username={}\",\n    \"urlMain\": \"https://ourdjtalk.com/\",\n    \"username_claimed\": \"steve\"\n  },\n  \"Outgress\": {\n    \"errorMsg\": \"Outgress - Error\",\n    \"errorType\": \"message\",\n    \"url\": \"https://outgress.com/agents/{}\",\n    \"urlMain\": \"https://outgress.com/\",\n    \"username_claimed\": \"pylapp\"\n  },\n  \"PCGamer\": {\n    \"errorMsg\": \"The specified member cannot be found. Please enter a member's entire name.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://forums.pcgamer.com/members/?username={}\",\n    \"urlMain\": \"https://pcgamer.com\",\n    \"username_claimed\": \"admin\"\n  },\n  \"PSNProfiles.com\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://psnprofiles.com/?psnId={}\",\n    \"url\": \"https://psnprofiles.com/{}\",\n    \"urlMain\": \"https://psnprofiles.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Packagist\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://packagist.org/search/?q={}&reason=vendor_not_found\",\n    \"url\": \"https://packagist.org/packages/{}/\",\n    \"urlMain\": \"https://packagist.org/\",\n    \"username_claimed\": \"psr\"\n  },\n  \"Pastebin\": {\n    \"errorMsg\": \"Not Found (#404)\",\n    \"errorType\": \"message\",\n    \"url\": \"https://pastebin.com/u/{}\",\n    \"urlMain\": \"https://pastebin.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Patched\": {\n    \"errorMsg\": \"The member you specified is either invalid or doesn't exist.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://patched.sh/User/{}\",\n    \"urlMain\": \"https://patched.sh/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Patreon\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.patreon.com/{}\",\n    \"urlMain\": \"https://www.patreon.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"PentesterLab\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[\\\\w]{4,30}$\",\n    \"url\": \"https://pentesterlab.com/profile/{}\",\n    \"urlMain\": \"https://pentesterlab.com/\",\n    \"username_claimed\": \"0day\"\n  },\n  \"HotUKdeals\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.hotukdeals.com/profile/{}\",\n    \"urlMain\": \"https://www.hotukdeals.com/\",\n    \"username_claimed\": \"Blue\",\n    \"request_method\": \"GET\"\n  },\n  \"Mydealz\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.mydealz.de/profile/{}\",\n    \"urlMain\": \"https://www.mydealz.de/\",\n    \"username_claimed\": \"blue\",\n    \"request_method\": \"GET\"\n  },\n  \"Chollometro\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.chollometro.com/profile/{}\",\n    \"urlMain\": \"https://www.chollometro.com/\",\n    \"username_claimed\": \"blue\",\n    \"request_method\": \"GET\"\n  },\n  \"PepperNL\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://nl.pepper.com/profile/{}\",\n    \"urlMain\": \"https://nl.pepper.com/\",\n    \"username_claimed\": \"Dynaw\",\n    \"request_method\": \"GET\"\n  },\n  \"PepperPL\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.pepper.pl/profile/{}\",\n    \"urlMain\": \"https://www.pepper.pl/\",\n    \"username_claimed\": \"FireChicken\",\n    \"request_method\": \"GET\"\n  },\n  \"Preisjaeger\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.preisjaeger.at/profile/{}\",\n    \"urlMain\": \"https://www.preisjaeger.at/\",\n    \"username_claimed\": \"Stefan\",\n    \"request_method\": \"GET\"\n  },\n  \"Pepperdeals\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.pepperdeals.se/profile/{}\",\n    \"urlMain\": \"https://www.pepperdeals.se/\",\n    \"username_claimed\": \"Mark\",\n    \"request_method\": \"GET\"\n  },\n  \"PepperealsUS\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.pepperdeals.com/profile/{}\",\n    \"urlMain\": \"https://www.pepperdeals.com/\",\n    \"username_claimed\": \"Stepan\",\n    \"request_method\": \"GET\"\n  },\n  \"Promodescuentos\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.promodescuentos.com/profile/{}\",\n    \"urlMain\": \"https://www.promodescuentos.com/\",\n    \"username_claimed\": \"blue\",\n    \"request_method\": \"GET\"\n  },\n  \"Periscope\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.periscope.tv/{}/\",\n    \"urlMain\": \"https://www.periscope.tv/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Pinkbike\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[^.]*?$\",\n    \"url\": \"https://www.pinkbike.com/u/{}/\",\n    \"urlMain\": \"https://www.pinkbike.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"pixelfed.social\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://pixelfed.social/{}/\",\n    \"urlMain\": \"https://pixelfed.social\",\n    \"username_claimed\": \"pylapp\"\n  },\n  \"PlayStore\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://play.google.com/store/apps/developer?id={}\",\n    \"urlMain\": \"https://play.google.com/store\",\n    \"username_claimed\": \"Facebook\"\n  },\n  \"Playstrategy\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://playstrategy.org/@/{}\",\n    \"urlMain\": \"https://playstrategy.org\",\n    \"username_claimed\": \"oruro\"\n  },\n  \"Plurk\": {\n    \"errorMsg\": \"User Not Found!\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.plurk.com/{}\",\n    \"urlMain\": \"https://www.plurk.com/\",\n    \"username_claimed\": \"plurkoffice\"\n  },\n  \"PocketStars\": {\n    \"errorMsg\": \"Join Your Favorite Adult Stars\",\n    \"errorType\": \"message\",\n    \"isNSFW\": true,\n    \"url\": \"https://pocketstars.com/{}\",\n    \"urlMain\": \"https://pocketstars.com/\",\n    \"username_claimed\": \"hacker\"\n  },\n  \"Pokemon Showdown\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://pokemonshowdown.com/users/{}\",\n    \"urlMain\": \"https://pokemonshowdown.com\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Polarsteps\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://polarsteps.com/{}\",\n    \"urlMain\": \"https://polarsteps.com/\",\n    \"urlProbe\": \"https://api.polarsteps.com/users/byusername/{}\",\n    \"username_claimed\": \"james\"\n  },\n  \"Polygon\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.polygon.com/users/{}\",\n    \"urlMain\": \"https://www.polygon.com/\",\n    \"username_claimed\": \"swiftstickler\"\n  },\n  \"Polymart\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://polymart.org/user/-1\",\n    \"url\": \"https://polymart.org/user/{}\",\n    \"urlMain\": \"https://polymart.org/\",\n    \"username_claimed\": \"craciu25yt\"\n  },\n  \"Pornhub\": {\n    \"errorType\": \"status_code\",\n    \"isNSFW\": true,\n    \"url\": \"https://pornhub.com/users/{}\",\n    \"urlMain\": \"https://pornhub.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"ProductHunt\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.producthunt.com/@{}\",\n    \"urlMain\": \"https://www.producthunt.com/\",\n    \"username_claimed\": \"jenny\"\n  },\n  \"programming.dev\": {\n    \"errorMsg\": \"Error!\",\n    \"errorType\": \"message\",\n    \"url\": \"https://programming.dev/u/{}\",\n    \"urlMain\": \"https://programming.dev\",\n    \"username_claimed\": \"pylapp\"\n  },\n  \"Pychess\": {\n    \"errorType\": \"message\",\n    \"errorMsg\": \"404\",\n    \"url\": \"https://www.pychess.org/@/{}\",\n    \"urlMain\": \"https://www.pychess.org\",\n    \"username_claimed\": \"gbtami\"\n  },\n  \"PromoDJ\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"http://promodj.com/{}\",\n    \"urlMain\": \"http://promodj.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Pronouns.page\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://pronouns.page/@{}\",\n    \"urlMain\": \"https://pronouns.page/\",\n    \"username_claimed\": \"andrea\"\n  },\n  \"PyPi\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://pypi.org/user/{}\",\n    \"urlProbe\": \"https://pypi.org/_includes/administer-user-include/{}\",\n    \"urlMain\": \"https://pypi.org\",\n    \"username_claimed\": \"Blue\"\n  },\n  \"Python.org Discussions\": {\n    \"errorMsg\": \"Oops! That page doesn’t exist or is private.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://discuss.python.org/u/{}/summary\",\n    \"urlMain\": \"https://discuss.python.org\",\n    \"username_claimed\": \"pablogsal\"\n  },\n  \"Rajce.net\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[\\\\w@-]+?$\",\n    \"url\": \"https://{}.rajce.idnes.cz/\",\n    \"urlMain\": \"https://www.rajce.idnes.cz/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Rarible\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://rarible.com/marketplace/api/v4/urls/{}\",\n    \"urlMain\": \"https://rarible.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Rate Your Music\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://rateyourmusic.com/~{}\",\n    \"urlMain\": \"https://rateyourmusic.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Rclone Forum\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://forum.rclone.org/u/{}\",\n    \"urlMain\": \"https://forum.rclone.org/\",\n    \"username_claimed\": \"ncw\"\n  },\n  \"RedTube\": {\n    \"errorType\": \"status_code\",\n    \"isNSFW\": true,\n    \"url\": \"https://www.redtube.com/users/{}\",\n    \"urlMain\": \"https://www.redtube.com/\",\n    \"username_claimed\": \"hacker\"\n  },\n  \"Redbubble\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.redbubble.com/people/{}\",\n    \"urlMain\": \"https://www.redbubble.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Reddit\": {\n    \"errorMsg\": \"Sorry, nobody on Reddit goes by that name.\",\n    \"errorType\": \"message\",\n    \"headers\": {\n      \"accept-language\": \"en-US,en;q=0.9\"\n    },\n    \"url\": \"https://www.reddit.com/user/{}\",\n    \"urlMain\": \"https://www.reddit.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Realmeye\": {\n    \"errorMsg\": \"Sorry, but we either:\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.realmeye.com/player/{}\",\n    \"urlMain\": \"https://www.realmeye.com/\",\n    \"username_claimed\": \"rotmg\"\n  },\n  \"Reisefrage\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.reisefrage.net/nutzer/{}\",\n    \"urlMain\": \"https://www.reisefrage.net/\",\n    \"username_claimed\": \"reisefrage\"\n  },\n  \"Replit.com\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://replit.com/@{}\",\n    \"urlMain\": \"https://replit.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"ResearchGate\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://www.researchgate.net/directory/profiles\",\n    \"regexCheck\": \"\\\\w+_\\\\w+\",\n    \"url\": \"https://www.researchgate.net/profile/{}\",\n    \"urlMain\": \"https://www.researchgate.net/\",\n    \"username_claimed\": \"John_Smith\"\n  },\n  \"ReverbNation\": {\n    \"errorMsg\": \"Sorry, we couldn't find that page\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.reverbnation.com/{}\",\n    \"urlMain\": \"https://www.reverbnation.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Roblox\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.roblox.com/user.aspx?username={}\",\n    \"urlMain\": \"https://www.roblox.com/\",\n    \"username_claimed\": \"bluewolfekiller\"\n  },\n  \"RocketTube\": {\n    \"errorMsg\": \"OOPS! Houston, we have a problem\",\n    \"errorType\": \"message\",\n    \"isNSFW\": true,\n    \"url\": \"https://www.rockettube.com/{}\",\n    \"urlMain\": \"https://www.rockettube.com/\",\n    \"username_claimed\": \"Tatteddick5600\"\n  },\n  \"RoyalCams\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://royalcams.com/profile/{}\",\n    \"urlMain\": \"https://royalcams.com\",\n    \"username_claimed\": \"asuna-black\"\n  },\n  \"Ruby Forums\": {\n    \"errorMsg\": \"Oops! That page doesn’t exist or is private.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://ruby-forum.com/u/{}/summary\",\n    \"urlMain\": \"https://ruby-forums.com\",\n    \"username_claimed\": \"rishard\"\n  },\n  \"RubyGems\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z][a-zA-Z0-9_-]{1,40}\",\n    \"url\": \"https://rubygems.org/profiles/{}\",\n    \"urlMain\": \"https://rubygems.org/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Rumble\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://rumble.com/user/{}\",\n    \"urlMain\": \"https://rumble.com/\",\n    \"username_claimed\": \"John\"\n  },\n  \"RuneScape\": {\n    \"errorMsg\": \"{\\\"error\\\":\\\"NO_PROFILE\\\",\\\"loggedIn\\\":\\\"false\\\"}\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^(?! )[\\\\w -]{1,12}(?<! )$\",\n    \"url\": \"https://apps.runescape.com/runemetrics/app/overview/player/{}\",\n    \"urlMain\": \"https://www.runescape.com/\",\n    \"urlProbe\": \"https://apps.runescape.com/runemetrics/profile/profile?user={}\",\n    \"username_claimed\": \"L33\"\n  },\n  \"SWAPD\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://swapd.co/u/{}\",\n    \"urlMain\": \"https://swapd.co/\",\n    \"username_claimed\": \"swapd\"\n  },\n  \"Sbazar.cz\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.sbazar.cz/{}\",\n    \"urlMain\": \"https://www.sbazar.cz/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Scratch\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://scratch.mit.edu/users/{}\",\n    \"urlMain\": \"https://scratch.mit.edu/\",\n    \"username_claimed\": \"griffpatch\"\n  },\n  \"Scribd\": {\n    \"errorMsg\": \"Page not found\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.scribd.com/{}\",\n    \"urlMain\": \"https://www.scribd.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"SEOForum\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://seoforum.com/@{}\",\n    \"urlMain\": \"https://www.seoforum.com/\",\n    \"username_claimed\": \"ko\"\n  },\n  \"ShitpostBot5000\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.shitpostbot.com/user/{}\",\n    \"urlMain\": \"https://www.shitpostbot.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Signal\": {\n    \"errorMsg\": \"Oops! That page doesn\\u2019t exist or is private.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://community.signalusers.org/u/{}\",\n    \"urlMain\": \"https://community.signalusers.org\",\n    \"username_claimed\": \"jlund\"\n  },\n  \"Sketchfab\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://sketchfab.com/{}\",\n    \"urlMain\": \"https://sketchfab.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Slack\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z][a-zA-Z0-9_-]*$\",\n    \"url\": \"https://{}.slack.com\",\n    \"urlMain\": \"https://slack.com\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Slant\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^.{2,32}$\",\n    \"url\": \"https://www.slant.co/users/{}\",\n    \"urlMain\": \"https://www.slant.co/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Slashdot\": {\n    \"errorMsg\": \"user you requested does not exist\",\n    \"errorType\": \"message\",\n    \"url\": \"https://slashdot.org/~{}\",\n    \"urlMain\": \"https://slashdot.org\",\n    \"username_claimed\": \"blue\"\n  },\n  \"SlideShare\": {\n    \"errorType\": \"message\",\n    \"errorMsg\": \"<title>Page no longer exists</title>\",\n    \"url\": \"https://slideshare.net/{}\",\n    \"urlMain\": \"https://slideshare.net/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Slides\": {\n    \"errorCode\": 204,\n    \"errorType\": \"status_code\",\n    \"url\": \"https://slides.com/{}\",\n    \"urlMain\": \"https://slides.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"SmugMug\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z]{1,35}$\",\n    \"url\": \"https://{}.smugmug.com\",\n    \"urlMain\": \"https://smugmug.com\",\n    \"username_claimed\": \"winchester\"\n  },\n  \"Smule\": {\n    \"errorMsg\": \"Smule | Page Not Found (404)\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.smule.com/{}\",\n    \"urlMain\": \"https://www.smule.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Snapchat\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-z][a-z-_.]{3,15}\",\n    \"request_method\": \"GET\",\n    \"url\": \"https://www.snapchat.com/add/{}\",\n    \"urlMain\": \"https://www.snapchat.com\",\n    \"username_claimed\": \"teamsnapchat\"\n  },\n  \"SOOP\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.sooplive.co.kr/station/{}\",\n    \"urlMain\": \"https://www.sooplive.co.kr/\",\n    \"urlProbe\": \"https://api-channel.sooplive.co.kr/v1.1/channel/{}/station\",\n    \"username_claimed\": \"udkn\"\n  },\n  \"SoundCloud\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://soundcloud.com/{}\",\n    \"urlMain\": \"https://soundcloud.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"SourceForge\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://sourceforge.net/u/{}\",\n    \"urlMain\": \"https://sourceforge.net/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"SoylentNews\": {\n    \"errorMsg\": \"The user you requested does not exist, no matter how much you wish this might be the case.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://soylentnews.org/~{}\",\n    \"urlMain\": \"https://soylentnews.org\",\n    \"username_claimed\": \"adam\"\n  },\n  \"SpeakerDeck\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://speakerdeck.com/{}\",\n    \"urlMain\": \"https://speakerdeck.com/\",\n    \"username_claimed\": \"pylapp\"\n  },\n  \"Speedrun.com\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://speedrun.com/users/{}\",\n    \"urlMain\": \"https://speedrun.com/\",\n    \"username_claimed\": \"example\"\n  },\n  \"Spells8\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://forum.spells8.com/u/{}\",\n    \"urlMain\": \"https://spells8.com\",\n    \"username_claimed\": \"susurrus\"\n  },\n  \"Splice\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://splice.com/{}\",\n    \"urlMain\": \"https://splice.com/\",\n    \"username_claimed\": \"splice\"\n  },\n  \"Splits.io\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[^.]*?$\",\n    \"url\": \"https://splits.io/users/{}\",\n    \"urlMain\": \"https://splits.io\",\n    \"username_claimed\": \"cambosteve\"\n  },\n  \"Sporcle\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.sporcle.com/user/{}/people\",\n    \"urlMain\": \"https://www.sporcle.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Sportlerfrage\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.sportlerfrage.net/nutzer/{}\",\n    \"urlMain\": \"https://www.sportlerfrage.net/\",\n    \"username_claimed\": \"sportlerfrage\"\n  },\n  \"SportsRU\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.sports.ru/profile/{}/\",\n    \"urlMain\": \"https://www.sports.ru/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Spotify\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://open.spotify.com/user/{}\",\n    \"urlMain\": \"https://open.spotify.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Star Citizen\": {\n    \"errorMsg\": \"404\",\n    \"errorType\": \"message\",\n    \"url\": \"https://robertsspaceindustries.com/citizens/{}\",\n    \"urlMain\": \"https://robertsspaceindustries.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Status Cafe\": {\n    \"errorMsg\": \"Page Not Found\",\n    \"errorType\": \"message\",\n    \"url\": \"https://status.cafe/users/{}\",\n    \"urlMain\": \"https://status.cafe/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Steam Community (Group)\": {\n    \"errorMsg\": \"No group could be retrieved for the given URL\",\n    \"errorType\": \"message\",\n    \"url\": \"https://steamcommunity.com/groups/{}\",\n    \"urlMain\": \"https://steamcommunity.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Steam Community (User)\": {\n    \"errorMsg\": \"The specified profile could not be found\",\n    \"errorType\": \"message\",\n    \"url\": \"https://steamcommunity.com/id/{}/\",\n    \"urlMain\": \"https://steamcommunity.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Strava\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[^.]*?$\",\n    \"url\": \"https://www.strava.com/athletes/{}\",\n    \"urlMain\": \"https://www.strava.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"SublimeForum\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://forum.sublimetext.com/u/{}\",\n    \"urlMain\": \"https://forum.sublimetext.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"TETR.IO\": {\n    \"errorMsg\": \"No such user!\",\n    \"errorType\": \"message\",\n    \"url\": \"https://ch.tetr.io/u/{}\",\n    \"urlMain\": \"https://tetr.io\",\n    \"urlProbe\": \"https://ch.tetr.io/api/users/{}\",\n    \"username_claimed\": \"osk\"\n  },\n  \"TheMovieDB\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.themoviedb.org/u/{}\",\n    \"urlMain\": \"https://www.themoviedb.org/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"TikTok\": {\n    \"url\": \"https://www.tiktok.com/@{}\",\n    \"urlMain\": \"https://www.tiktok.com\",\n    \"errorType\": \"message\",\n    \"errorMsg\": [\n      \"\\\"statusCode\\\":10221\",\n      \"Govt. of India decided to block 59 apps\"\n    ],\n    \"username_claimed\": \"charlidamelio\"\n  },\n  \"Tiendanube\": {\n    \"url\": \"https://{}.mitiendanube.com/\",\n    \"urlMain\": \"https://www.tiendanube.com/\",\n    \"errorType\": \"status_code\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Topcoder\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://profiles.topcoder.com/{}/\",\n    \"urlMain\": \"https://topcoder.com/\",\n    \"username_claimed\": \"USER\",\n    \"urlProbe\": \"https://api.topcoder.com/v5/members/{}\",\n    \"regexCheck\": \"^[a-zA-Z0-9_.]+$\"\n  },\n  \"Topmate\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://topmate.io/{}\",\n    \"urlMain\": \"https://topmate.io/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"TRAKTRAIN\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://traktrain.com/{}\",\n    \"urlMain\": \"https://traktrain.com/\",\n    \"username_claimed\": \"traktrain\"\n  },\n  \"Telegram\": {\n    \"errorMsg\": [\n      \"<title>Telegram Messenger</title>\",\n      \"If you have <strong>Telegram</strong>, you can contact <a class=\\\"tgme_username_link\\\" href=\\\"tg://resolve?domain=\"\n    ],\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[a-zA-Z0-9_]{3,32}[^_]$\",\n    \"url\": \"https://t.me/{}\",\n    \"urlMain\": \"https://t.me/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Tellonym.me\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://tellonym.me/{}\",\n    \"urlMain\": \"https://tellonym.me/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Tenor\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[A-Za-z0-9_]{2,32}$\",\n    \"url\": \"https://tenor.com/users/{}\",\n    \"urlMain\": \"https://tenor.com/\",\n    \"username_claimed\": \"red\"\n  },\n  \"Terraria Forums\": {\n    \"errorMsg\": \"The following members could not be found\",\n    \"errorType\": \"message\",\n    \"url\": \"https://forums.terraria.org/index.php?search/42798315/&c[users]={}&o=relevance\",\n    \"urlMain\": \"https://forums.terraria.org/index.php\",\n    \"username_claimed\": \"blue\"\n  },\n  \"ThemeForest\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://themeforest.net/user/{}\",\n    \"urlMain\": \"https://themeforest.net/\",\n    \"username_claimed\": \"user\"\n  },\n  \"tistory\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://{}.tistory.com/\",\n    \"urlMain\": \"https://www.tistory.com/\",\n    \"username_claimed\": \"notice\"\n  },\n  \"TnAFlix\": {\n    \"errorType\": \"status_code\",\n    \"isNSFW\": true,\n    \"url\": \"https://www.tnaflix.com/profile/{}\",\n    \"urlMain\": \"https://www.tnaflix.com/\",\n    \"username_claimed\": \"hacker\"\n  },\n  \"TradingView\": {\n    \"errorType\": \"status_code\",\n    \"request_method\": \"GET\",\n    \"url\": \"https://www.tradingview.com/u/{}/\",\n    \"urlMain\": \"https://www.tradingview.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Trakt\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[^.]*$\",\n    \"url\": \"https://www.trakt.tv/users/{}\",\n    \"urlMain\": \"https://www.trakt.tv/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"TrashboxRU\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[A-Za-z0-9_-]{3,16}$\",\n    \"url\": \"https://trashbox.ru/users/{}\",\n    \"urlMain\": \"https://trashbox.ru/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Trawelling\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://traewelling.de/@{}\",\n    \"urlMain\": \"https://traewelling.de/\",\n    \"username_claimed\": \"lassestolley\"\n  },\n  \"Trello\": {\n    \"errorMsg\": \"model not found\",\n    \"errorType\": \"message\",\n    \"url\": \"https://trello.com/{}\",\n    \"urlMain\": \"https://trello.com/\",\n    \"urlProbe\": \"https://trello.com/1/Members/{}\",\n    \"username_claimed\": \"blue\"\n  },\n  \"TryHackMe\": {\n    \"errorMsg\": \"{\\\"success\\\":false}\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[a-zA-Z0-9.]{1,16}$\",\n    \"url\": \"https://tryhackme.com/p/{}\",\n    \"urlMain\": \"https://tryhackme.com/\",\n    \"urlProbe\": \"https://tryhackme.com/api/user/exist/{}\",\n    \"username_claimed\": \"ashu\"\n  },\n  \"Tuna\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-z0-9]{4,40}$\",\n    \"url\": \"https://tuna.voicemod.net/user/{}\",\n    \"urlMain\": \"https://tuna.voicemod.net/\",\n    \"username_claimed\": \"bob\"\n  },\n  \"Tweakers\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://tweakers.net/gallery/{}\",\n    \"urlMain\": \"https://tweakers.net\",\n    \"username_claimed\": \"femme\"\n  },\n  \"Twitch\": {\n    \"errorMsg\": \"content='Twitch is the world&#39;s leading video platform and community for gamers.'\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.twitch.tv/{}\",\n    \"urlMain\": \"https://www.twitch.tv\",\n    \"username_claimed\": \"xqc\"\n  },\n\n  \"Trovo\": {\n    \"errorMsg\": \"Uh Ohhh...\",\n    \"errorType\": \"message\",\n    \"url\": \"https://trovo.live/s/{}/\",\n    \"urlMain\": \"https://trovo.live\",\n    \"username_claimed\": \"Aimilios\"\n  },\n  \"Twitter\": {\n    \"errorMsg\": [\n      \"<div class=\\\"error-panel\\\"><span>User \",\n      \"<title>429 Too Many Requests</title>\"\n    ],\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[a-zA-Z0-9_]{1,15}$\",\n    \"url\": \"https://x.com/{}\",\n    \"urlMain\": \"https://x.com/\",\n    \"urlProbe\": \"https://nitter.privacydev.net/{}\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Typeracer\": {\n    \"errorMsg\": \"Profile Not Found\",\n    \"errorType\": \"message\",\n    \"url\": \"https://data.typeracer.com/pit/profile?user={}\",\n    \"urlMain\": \"https://typeracer.com\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Ultimate-Guitar\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://ultimate-guitar.com/u/{}\",\n    \"urlMain\": \"https://ultimate-guitar.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Unsplash\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-z0-9_]{1,60}$\",\n    \"url\": \"https://unsplash.com/@{}\",\n    \"urlMain\": \"https://unsplash.com/\",\n    \"username_claimed\": \"jenny\"\n  },\n  \"Untappd\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://untappd.com/user/{}\",\n    \"urlMain\": \"https://untappd.com/\",\n    \"username_claimed\": \"untappd\"\n  },\n  \"Valorant Forums\": {\n    \"errorMsg\": \"The page you requested could not be found.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://valorantforums.com/u/{}\",\n    \"urlMain\": \"https://valorantforums.com\",\n    \"username_claimed\": \"Wolves\"\n  },\n  \"VK\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://www.quora.com/profile/{}\",\n    \"url\": \"https://vk.com/{}\",\n    \"urlMain\": \"https://vk.com/\",\n    \"username_claimed\": \"brown\"\n  },\n  \"VSCO\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://vsco.co/{}\",\n    \"urlMain\": \"https://vsco.co/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Velog\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://velog.io/@{}/posts\",\n    \"urlMain\": \"https://velog.io/\",\n    \"username_claimed\": \"qlgks1\"\n  },\n  \"Velomania\": {\n    \"errorMsg\": \"\\u041f\\u043e\\u043b\\u044c\\u0437\\u043e\\u0432\\u0430\\u0442\\u0435\\u043b\\u044c \\u043d\\u0435 \\u0437\\u0430\\u0440\\u0435\\u0433\\u0438\\u0441\\u0442\\u0440\\u0438\\u0440\\u043e\\u0432\\u0430\\u043d \\u0438 \\u043d\\u0435 \\u0438\\u043c\\u0435\\u0435\\u0442 \\u043f\\u0440\\u043e\\u0444\\u0438\\u043b\\u044f \\u0434\\u043b\\u044f \\u043f\\u0440\\u043e\\u0441\\u043c\\u043e\\u0442\\u0440\\u0430.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://forum.velomania.ru/member.php?username={}\",\n    \"urlMain\": \"https://forum.velomania.ru/\",\n    \"username_claimed\": \"red\"\n  },\n  \"Venmo\": {\n    \"errorMsg\": [\"Venmo | Page Not Found\"],\n    \"errorType\": \"message\",\n    \"headers\": {\n      \"Host\": \"account.venmo.com\"\n    },\n    \"url\": \"https://account.venmo.com/u/{}\",\n    \"urlMain\": \"https://venmo.com/\",\n    \"urlProbe\": \"https://test1.venmo.com/u/{}\",\n    \"username_claimed\": \"jenny\"\n  },\n  \"Vero\": {\n    \"errorMsg\": \"Not Found\",\n    \"errorType\": \"message\",\n    \"request_method\": \"GET\",\n    \"url\": \"https://vero.co/{}\",\n    \"urlMain\": \"https://vero.co/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Vimeo\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://vimeo.com/{}\",\n    \"urlMain\": \"https://vimeo.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"VirusTotal\": {\n    \"errorType\": \"status_code\",\n    \"request_method\": \"GET\",\n    \"url\": \"https://www.virustotal.com/gui/user/{}\",\n    \"urlMain\": \"https://www.virustotal.com/\",\n    \"urlProbe\": \"https://www.virustotal.com/ui/users/{}/avatar\",\n    \"username_claimed\": \"blue\"\n  },\n  \"VLR\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.vlr.gg/user/{}\",\n    \"urlMain\": \"https://www.vlr.gg\",\n    \"username_claimed\": \"optms\"\n  },\n  \"WICG Forum\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^(?![.-])[a-zA-Z0-9_.-]{3,20}$\",\n    \"url\": \"https://discourse.wicg.io/u/{}/summary\",\n    \"urlMain\": \"https://discourse.wicg.io/\",\n    \"username_claimed\": \"stefano\"\n  },\n  \"Wakatime\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://wakatime.com/@{}\",\n    \"urlMain\": \"https://wakatime.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Warrior Forum\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.warriorforum.com/members/{}.html\",\n    \"urlMain\": \"https://www.warriorforum.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Wattpad\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.wattpad.com/user/{}\",\n    \"urlMain\": \"https://www.wattpad.com/\",\n    \"urlProbe\": \"https://www.wattpad.com/api/v3/users/{}/\",\n    \"username_claimed\": \"Dogstho7951\"\n  },\n  \"WebNode\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[\\\\w@-]+?$\",\n    \"url\": \"https://{}.webnode.cz/\",\n    \"urlMain\": \"https://www.webnode.cz/\",\n    \"username_claimed\": \"radkabalcarova\"\n  },\n  \"Weblate\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z0-9@._-]{1,150}$\",\n    \"url\": \"https://hosted.weblate.org/user/{}/\",\n    \"urlMain\": \"https://hosted.weblate.org/\",\n    \"username_claimed\": \"adam\"\n  },\n  \"Weebly\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[a-zA-Z0-9-]{1,63}$\",\n    \"url\": \"https://{}.weebly.com/\",\n    \"urlMain\": \"https://weebly.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Wikidot\": {\n    \"errorMsg\": \"User does not exist.\",\n    \"errorType\": \"message\",\n    \"url\": \"http://www.wikidot.com/user:info/{}\",\n    \"urlMain\": \"http://www.wikidot.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Wikipedia\": {\n    \"errorMsg\": \"centralauth-admin-nonexistent:\",\n    \"errorType\": \"message\",\n    \"url\": \"https://en.wikipedia.org/wiki/Special:CentralAuth/{}?uselang=qqx\",\n    \"urlMain\": \"https://www.wikipedia.org/\",\n    \"username_claimed\": \"Hoadlck\"\n  },\n  \"Windy\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://community.windy.com/user/{}\",\n    \"urlMain\": \"https://windy.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Wix\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[\\\\w@-]+?$\",\n    \"url\": \"https://{}.wix.com\",\n    \"urlMain\": \"https://wix.com/\",\n    \"username_claimed\": \"support\"\n  },\n  \"WolframalphaForum\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://community.wolfram.com/web/{}/home\",\n    \"urlMain\": \"https://community.wolfram.com/\",\n    \"username_claimed\": \"unico\"\n  },\n  \"WordPress\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"wordpress.com/typo/?subdomain=\",\n    \"regexCheck\": \"^[a-zA-Z][a-zA-Z0-9_-]*$\",\n    \"url\": \"https://{}.wordpress.com/\",\n    \"urlMain\": \"https://wordpress.com\",\n    \"username_claimed\": \"blue\"\n  },\n  \"WordPressOrg\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://wordpress.org\",\n    \"url\": \"https://profiles.wordpress.org/{}/\",\n    \"urlMain\": \"https://wordpress.org/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Wordnik\": {\n    \"errorMsg\": \"Page Not Found\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[a-zA-Z0-9_.+-]{1,40}$\",\n    \"url\": \"https://www.wordnik.com/users/{}\",\n    \"urlMain\": \"https://www.wordnik.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Wykop\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.wykop.pl/ludzie/{}\",\n    \"urlMain\": \"https://www.wykop.pl\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Xbox Gamertag\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://xboxgamertag.com/search/{}\",\n    \"urlMain\": \"https://xboxgamertag.com/\",\n    \"username_claimed\": \"red\"\n  },\n  \"Xvideos\": {\n    \"errorType\": \"status_code\",\n    \"isNSFW\": true,\n    \"url\": \"https://xvideos.com/profiles/{}\",\n    \"urlMain\": \"https://xvideos.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"YandexMusic\": {\n    \"__comment__\": \"The first and third errorMsg relate to geo-restrictions and bot detection/captchas.\",\n    \"errorMsg\": [\n      \"\\u041e\\u0448\\u0438\\u0431\\u043a\\u0430 404\",\n      \"<meta name=\\\"description\\\" content=\\\"\\u041e\\u0442\\u043a\\u0440\\u044b\\u0432\\u0430\\u0439\\u0442\\u0435 \\u043d\\u043e\\u0432\\u0443\\u044e \\u043c\\u0443\\u0437\\u044b\\u043a\\u0443 \\u043a\\u0430\\u0436\\u0434\\u044b\\u0439 \\u0434\\u0435\\u043d\\u044c.\",\n      \"<input type=\\\"submit\\\" class=\\\"CheckboxCaptcha-Button\\\"\"\n    ],\n    \"errorType\": \"message\",\n    \"url\": \"https://music.yandex/users/{}/playlists\",\n    \"urlMain\": \"https://music.yandex\",\n    \"username_claimed\": \"ya.playlist\"\n  },\n  \"YouNow\": {\n    \"errorMsg\": \"No users found\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.younow.com/{}/\",\n    \"urlMain\": \"https://www.younow.com/\",\n    \"urlProbe\": \"https://api.younow.com/php/api/broadcast/info/user={}/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"YouPic\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://youpic.com/photographer/{}/\",\n    \"urlMain\": \"https://youpic.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"YouPorn\": {\n    \"errorType\": \"status_code\",\n    \"isNSFW\": true,\n    \"url\": \"https://youporn.com/uservids/{}\",\n    \"urlMain\": \"https://youporn.com\",\n    \"username_claimed\": \"blue\"\n  },\n  \"YouTube\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.youtube.com/@{}\",\n    \"urlMain\": \"https://www.youtube.com/\",\n    \"username_claimed\": \"youtube\"\n  },\n  \"akniga\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://akniga.org/profile/{}\",\n    \"urlMain\": \"https://akniga.org/profile/blue/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"authorSTREAM\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"http://www.authorstream.com/{}/\",\n    \"urlMain\": \"http://www.authorstream.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"babyblogRU\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://www.babyblog.ru/\",\n    \"url\": \"https://www.babyblog.ru/user/{}\",\n    \"urlMain\": \"https://www.babyblog.ru/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"chaos.social\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://chaos.social/@{}\",\n    \"urlMain\": \"https://chaos.social/\",\n    \"username_claimed\": \"rixx\"\n  },\n  \"couchsurfing\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.couchsurfing.com/people/{}\",\n    \"urlMain\": \"https://www.couchsurfing.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"d3RU\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://d3.ru/user/{}/posts\",\n    \"urlMain\": \"https://d3.ru/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"dailykos\": {\n    \"errorMsg\": \"{\\\"result\\\":true,\\\"message\\\":null}\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.dailykos.com/user/{}\",\n    \"urlMain\": \"https://www.dailykos.com\",\n    \"urlProbe\": \"https://www.dailykos.com/signup/check_nickname?nickname={}\",\n    \"username_claimed\": \"blue\"\n  },\n  \"datingRU\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"http://dating.ru/{}\",\n    \"urlMain\": \"http://dating.ru\",\n    \"username_claimed\": \"blue\"\n  },\n  \"devRant\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://devrant.com/\",\n    \"url\": \"https://devrant.com/users/{}\",\n    \"urlMain\": \"https://devrant.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"drive2\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.drive2.ru/users/{}\",\n    \"urlMain\": \"https://www.drive2.ru/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"eGPU\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://egpu.io/forums/profile/{}/\",\n    \"urlMain\": \"https://egpu.io/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"eintracht\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[^.]*?$\",\n    \"url\": \"https://community.eintracht.de/fans/{}\",\n    \"urlMain\": \"https://eintracht.de\",\n    \"username_claimed\": \"blue\"\n  },\n  \"fixya\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.fixya.com/users/{}\",\n    \"urlMain\": \"https://www.fixya.com\",\n    \"username_claimed\": \"adam\"\n  },\n  \"fl\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.fl.ru/users/{}\",\n    \"urlMain\": \"https://www.fl.ru/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"forum_guns\": {\n    \"errorMsg\": \"action=https://forum.guns.ru/forummisc/blog/search\",\n    \"errorType\": \"message\",\n    \"url\": \"https://forum.guns.ru/forummisc/blog/{}\",\n    \"urlMain\": \"https://forum.guns.ru/\",\n    \"username_claimed\": \"red\"\n  },\n  \"freecodecamp\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.freecodecamp.org/{}\",\n    \"urlMain\": \"https://www.freecodecamp.org/\",\n    \"urlProbe\": \"https://api.freecodecamp.org/api/users/get-public-profile?username={}\",\n    \"username_claimed\": \"naveennamani\"\n  },\n  \"furaffinity\": {\n    \"errorMsg\": \"This user cannot be found.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.furaffinity.net/user/{}\",\n    \"urlMain\": \"https://www.furaffinity.net\",\n    \"username_claimed\": \"jesus\"\n  },\n  \"geocaching\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.geocaching.com/p/default.aspx?u={}\",\n    \"urlMain\": \"https://www.geocaching.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"habr\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://habr.com/ru/users/{}\",\n    \"urlMain\": \"https://habr.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"hackster\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.hackster.io/{}\",\n    \"urlMain\": \"https://www.hackster.io\",\n    \"username_claimed\": \"blue\"\n  },\n  \"hunting\": {\n    \"errorMsg\": \"\\u0423\\u043a\\u0430\\u0437\\u0430\\u043d\\u043d\\u044b\\u0439 \\u043f\\u043e\\u043b\\u044c\\u0437\\u043e\\u0432\\u0430\\u0442\\u0435\\u043b\\u044c \\u043d\\u0435 \\u043d\\u0430\\u0439\\u0434\\u0435\\u043d. \\u041f\\u043e\\u0436\\u0430\\u043b\\u0443\\u0439\\u0441\\u0442\\u0430, \\u0432\\u0432\\u0435\\u0434\\u0438\\u0442\\u0435 \\u0434\\u0440\\u0443\\u0433\\u043e\\u0435 \\u0438\\u043c\\u044f.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.hunting.ru/forum/members/?username={}\",\n    \"urlMain\": \"https://www.hunting.ru/forum/\",\n    \"username_claimed\": \"red\"\n  },\n  \"igromania\": {\n    \"errorMsg\": \"\\u041f\\u043e\\u043b\\u044c\\u0437\\u043e\\u0432\\u0430\\u0442\\u0435\\u043b\\u044c \\u043d\\u0435 \\u0437\\u0430\\u0440\\u0435\\u0433\\u0438\\u0441\\u0442\\u0440\\u0438\\u0440\\u043e\\u0432\\u0430\\u043d \\u0438 \\u043d\\u0435 \\u0438\\u043c\\u0435\\u0435\\u0442 \\u043f\\u0440\\u043e\\u0444\\u0438\\u043b\\u044f \\u0434\\u043b\\u044f \\u043f\\u0440\\u043e\\u0441\\u043c\\u043e\\u0442\\u0440\\u0430.\",\n    \"errorType\": \"message\",\n    \"url\": \"http://forum.igromania.ru/member.php?username={}\",\n    \"urlMain\": \"http://forum.igromania.ru/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"interpals\": {\n    \"errorMsg\": \"The requested user does not exist or is inactive\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.interpals.net/{}\",\n    \"urlMain\": \"https://www.interpals.net/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"irecommend\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://irecommend.ru/users/{}\",\n    \"urlMain\": \"https://irecommend.ru/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"jbzd.com.pl\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://jbzd.com.pl/uzytkownik/{}\",\n    \"urlMain\": \"https://jbzd.com.pl/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"jeuxvideo\": {\n    \"errorType\": \"status_code\",\n    \"request_method\": \"GET\",\n    \"url\": \"https://www.jeuxvideo.com/profil/{}\",\n    \"urlMain\": \"https://www.jeuxvideo.com\",\n    \"urlProbe\": \"https://www.jeuxvideo.com/profil/{}?mode=infos\",\n    \"username_claimed\": \"adam\"\n  },\n  \"kofi\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://ko-fi.com/art?=redirect\",\n    \"url\": \"https://ko-fi.com/{}\",\n    \"urlMain\": \"https://ko-fi.com\",\n    \"username_claimed\": \"yeahkenny\"\n  },\n  \"kwork\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://kwork.ru/user/{}\",\n    \"urlMain\": \"https://www.kwork.ru/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"last.fm\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://last.fm/user/{}\",\n    \"urlMain\": \"https://last.fm/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"leasehackr\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://forum.leasehackr.com/u/{}/summary/\",\n    \"urlMain\": \"https://forum.leasehackr.com/\",\n    \"username_claimed\": \"adam\"\n  },\n  \"livelib\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.livelib.ru/reader/{}\",\n    \"urlMain\": \"https://www.livelib.ru/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"mastodon.cloud\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://mastodon.cloud/@{}\",\n    \"urlMain\": \"https://mastodon.cloud/\",\n    \"username_claimed\": \"TheAdmin\"\n  },\n  \"mastodon.social\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://mastodon.social/@{}\",\n    \"urlMain\": \"https://chaos.social/\",\n    \"username_claimed\": \"Gargron\"\n  },\n  \"mastodon.xyz\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://mastodon.xyz/@{}\",\n    \"urlMain\": \"https://mastodon.xyz/\",\n    \"username_claimed\": \"TheKinrar\"\n  },\n  \"mstdn.social\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://mstdn.social/@{}\",\n    \"urlMain\": \"https://mstdn.social/\",\n    \"username_claimed\": \"MagicLike\"\n  },\n  \"mercadolivre\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.mercadolivre.com.br/perfil/{}\",\n    \"urlMain\": \"https://www.mercadolivre.com.br\",\n    \"username_claimed\": \"blue\"\n  },\n  \"minds\": {\n    \"errorMsg\": \"\\\"valid\\\":true\",\n    \"errorType\": \"message\",\n    \"url\": \"https://www.minds.com/{}/\",\n    \"urlMain\": \"https://www.minds.com\",\n    \"urlProbe\": \"https://www.minds.com/api/v3/register/validate?username={}\",\n    \"username_claimed\": \"john\"\n  },\n  \"moikrug\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://moikrug.ru/{}\",\n    \"urlMain\": \"https://moikrug.ru/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"mstdn.io\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://mstdn.io/@{}\",\n    \"urlMain\": \"https://mstdn.io/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"nairaland.com\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.nairaland.com/{}\",\n    \"urlMain\": \"https://www.nairaland.com/\",\n    \"username_claimed\": \"red\"\n  },\n  \"n8n Community\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://community.n8n.io/u/{}/summary\",\n    \"urlMain\": \"https://community.n8n.io/\",\n    \"username_claimed\": \"n8n\"\n  },\n  \"nnRU\": {\n    \"errorType\": \"status_code\",\n    \"regexCheck\": \"^[\\\\w@-]+?$\",\n    \"url\": \"https://{}.www.nn.ru/\",\n    \"urlMain\": \"https://www.nn.ru/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"note\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://note.com/{}\",\n    \"urlMain\": \"https://note.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"npm\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.npmjs.com/~{}\",\n    \"urlMain\": \"https://www.npmjs.com/\",\n    \"username_claimed\": \"kennethsweezy\"\n  },\n  \"omg.lol\": {\n    \"errorMsg\": \"\\\"available\\\": true\",\n    \"errorType\": \"message\",\n    \"url\": \"https://{}.omg.lol\",\n    \"urlMain\": \"https://home.omg.lol\",\n    \"urlProbe\": \"https://api.omg.lol/address/{}/availability\",\n    \"username_claimed\": \"adam\"\n  },\n  \"opennet\": {\n    \"errorMsg\": \"\\u0418\\u043c\\u044f \\u0443\\u0447\\u0430\\u0441\\u0442\\u043d\\u0438\\u043a\\u0430 \\u043d\\u0435 \\u043d\\u0430\\u0439\\u0434\\u0435\\u043d\\u043e\",\n    \"errorType\": \"message\",\n    \"regexCheck\": \"^[^-]*$\",\n    \"url\": \"https://www.opennet.ru/~{}\",\n    \"urlMain\": \"https://www.opennet.ru/\",\n    \"username_claimed\": \"anonismus\"\n  },\n  \"osu!\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://osu.ppy.sh/users/{}\",\n    \"urlMain\": \"https://osu.ppy.sh/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"phpRU\": {\n    \"errorMsg\": \"\\u0423\\u043a\\u0430\\u0437\\u0430\\u043d\\u043d\\u044b\\u0439 \\u043f\\u043e\\u043b\\u044c\\u0437\\u043e\\u0432\\u0430\\u0442\\u0435\\u043b\\u044c \\u043d\\u0435 \\u043d\\u0430\\u0439\\u0434\\u0435\\u043d. \\u041f\\u043e\\u0436\\u0430\\u043b\\u0443\\u0439\\u0441\\u0442\\u0430, \\u0432\\u0432\\u0435\\u0434\\u0438\\u0442\\u0435 \\u0434\\u0440\\u0443\\u0433\\u043e\\u0435 \\u0438\\u043c\\u044f.\",\n    \"errorType\": \"message\",\n    \"url\": \"https://php.ru/forum/members/?username={}\",\n    \"urlMain\": \"https://php.ru/forum/\",\n    \"username_claimed\": \"apple\"\n  },\n  \"pikabu\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://pikabu.ru/@{}\",\n    \"urlMain\": \"https://pikabu.ru/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"Pinterest\": {\n    \"errorType\": \"status_code\",\n    \"errorUrl\": \"https://www.pinterest.com/\",\n    \"url\": \"https://www.pinterest.com/{}/\",\n    \"urlProbe\": \"https://www.pinterest.com/oembed.json?url=https://www.pinterest.com/{}/\",\n    \"urlMain\": \"https://www.pinterest.com/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"pr0gramm\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://pr0gramm.com/user/{}\",\n    \"urlMain\": \"https://pr0gramm.com/\",\n    \"urlProbe\": \"https://pr0gramm.com/api/profile/info?name={}\",\n    \"username_claimed\": \"cha0s\"\n  },\n  \"prog.hu\": {\n    \"errorType\": \"response_url\",\n    \"errorUrl\": \"https://prog.hu/azonosito/info/{}\",\n    \"url\": \"https://prog.hu/azonosito/info/{}\",\n    \"urlMain\": \"https://prog.hu/\",\n    \"username_claimed\": \"Sting\"\n  },\n  \"satsisRU\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://satsis.info/user/{}\",\n    \"urlMain\": \"https://satsis.info/\",\n    \"username_claimed\": \"red\"\n  },\n  \"sessionize\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://sessionize.com/{}\",\n    \"urlMain\": \"https://sessionize.com/\",\n    \"username_claimed\": \"jason-mayes\"\n  },\n  \"social.tchncs.de\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://social.tchncs.de/@{}\",\n    \"urlMain\": \"https://social.tchncs.de/\",\n    \"username_claimed\": \"Milan\"\n  },\n  \"spletnik\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://spletnik.ru/user/{}\",\n    \"urlMain\": \"https://spletnik.ru/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"svidbook\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.svidbook.ru/user/{}\",\n    \"urlMain\": \"https://www.svidbook.ru/\",\n    \"username_claimed\": \"green\"\n  },\n  \"threads\": {\n    \"errorMsg\": \"<title>Threads • Log in</title>\",\n    \"errorType\": \"message\",\n    \"headers\": {\n      \"Sec-Fetch-Mode\": \"navigate\"\n    },\n    \"url\": \"https://www.threads.net/@{}\",\n    \"urlMain\": \"https://www.threads.net/\",\n    \"username_claimed\": \"zuck\"\n  },\n  \"toster\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.toster.ru/user/{}/answers\",\n    \"urlMain\": \"https://www.toster.ru/\",\n    \"username_claimed\": \"adam\"\n  },\n  \"tumblr\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://{}.tumblr.com/\",\n    \"urlMain\": \"https://www.tumblr.com/\",\n    \"username_claimed\": \"goku\"\n  },\n  \"uid\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"http://uid.me/{}\",\n    \"urlMain\": \"https://uid.me/\",\n    \"username_claimed\": \"blue\"\n  },\n  \"write.as\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://write.as/{}\",\n    \"urlMain\": \"https://write.as\",\n    \"username_claimed\": \"pylapp\"\n  },\n  \"xHamster\": {\n    \"errorType\": \"status_code\",\n    \"isNSFW\": true,\n    \"url\": \"https://xhamster.com/users/{}\",\n    \"urlMain\": \"https://xhamster.com\",\n    \"urlProbe\": \"https://xhamster.com/users/{}?old_browser=true\",\n    \"username_claimed\": \"blue\"\n  },\n  \"znanylekarz.pl\": {\n    \"errorType\": \"status_code\",\n    \"url\": \"https://www.znanylekarz.pl/{}\",\n    \"urlMain\": \"https://znanylekarz.pl\",\n    \"username_claimed\": \"janusz-nowak\"\n  },\n  \"Platzi\": {\n    \"errorType\": \"status_code\",\n    \"errorCode\": 404,\n    \"url\": \"https://platzi.com/p/{}/\",\n    \"urlMain\": \"https://platzi.com/\",\n    \"username_claimed\": \"freddier\",\n    \"request_method\": \"GET\"\n  },\n  \"BabyRu\": {\n    \"url\": \"https://www.baby.ru/u/{}\",\n    \"urlMain\": \"https://www.baby.ru/\",\n    \"errorType\": \"message\",\n    \"errorMsg\": [\n      \"\\u0421\\u0442\\u0440\\u0430\\u043d\\u0438\\u0446\\u0430, \\u043a\\u043e\\u0442\\u043e\\u0440\\u0443\\u044e \\u0432\\u044b \\u0438\\u0441\\u043a\\u0430\\u043b\\u0438, \\u043d\\u0435 \\u043d\\u0430\\u0439\\u0434\\u0435\\u043d\\u0430\",\n      \"\\u0414\\u043e\\u0441\\u0442\\u0443\\u043f \\u0441 \\u0432\\u0430\\u0448\\u0435\\u0433\\u043e IP-\\u0430\\u0434\\u0440\\u0435\\u0441\\u0430 \\u0432\\u0440\\u0435\\u043c\\u0435\\u043d\\u043d\\u043e \\u043e\\u0433\\u0440\\u0430\\u043d\\u0438\\u0447\\u0435\\u043d\"\n    ],\n    \"username_claimed\": \"example\"\n  }\n}\n"
  },
  {
    "path": "the_big_brother/resources/data.schema.json",
    "content": "{\n  \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n  \"title\": \"Sherlock Target Manifest\",\n  \"description\": \"Social media targets to probe for the existence of known usernames\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"$schema\": { \"type\": \"string\" }\n  },\n  \"patternProperties\": {\n    \"^(?!\\\\$).*?$\": {\n      \"type\": \"object\",\n      \"description\": \"Target name and associated information (key should be human readable name)\",\n      \"required\": [\"url\", \"urlMain\", \"errorType\", \"username_claimed\"],\n      \"properties\": {\n        \"url\": { \"type\": \"string\" },\n        \"urlMain\": { \"type\": \"string\" },\n        \"urlProbe\": { \"type\": \"string\" },\n        \"username_claimed\": { \"type\": \"string\" },\n        \"regexCheck\": { \"type\": \"string\" },\n        \"isNSFW\": { \"type\": \"boolean\" },\n        \"headers\": { \"type\": \"object\" },\n        \"request_payload\": { \"type\": \"object\" },\n        \"__comment__\": {\n          \"type\": \"string\",\n          \"description\": \"Used to clarify important target information if (and only if) a commit message would not suffice.\\nThis key should not be parsed anywhere within Sherlock.\"\n        },\n        \"tags\": {\n          \"oneOf\": [\n            { \"$ref\": \"#/$defs/tag\" },\n            { \"type\": \"array\", \"items\": { \"$ref\": \"#/$defs/tag\" } }\n          ]\n        },\n        \"request_method\": {\n          \"type\": \"string\",\n          \"enum\": [\"GET\", \"POST\", \"HEAD\", \"PUT\"]\n        },\n        \"errorType\": {\n          \"oneOf\": [\n            {\n              \"type\": \"string\",\n              \"enum\": [\"message\", \"response_url\", \"status_code\"]\n            },\n            {\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"string\",\n                \"enum\": [\"message\", \"response_url\", \"status_code\"]\n              }\n            }\n          ]\n        },\n        \"errorMsg\": {\n          \"oneOf\": [\n            { \"type\": \"string\" },\n            { \"type\": \"array\", \"items\": { \"type\": \"string\" } }\n          ]\n        },\n        \"errorCode\": {\n          \"oneOf\": [\n            { \"type\": \"integer\" },\n            { \"type\": \"array\", \"items\": { \"type\": \"integer\" } }\n          ]\n        },\n        \"errorUrl\": { \"type\": \"string\" },\n        \"response_url\": { \"type\": \"string\" }\n      },\n      \"dependencies\": {\n        \"errorMsg\": {\n          \"oneOf\": [\n            { \"properties\": { \"errorType\": { \"const\": \"message\" } } },\n            {\n              \"properties\": {\n                \"errorType\": {\n                  \"type\": \"array\",\n                  \"contains\": { \"const\": \"message\" }\n                }\n              }\n            }\n          ]\n        },\n        \"errorUrl\": {\n          \"oneOf\": [\n            { \"properties\": { \"errorType\": { \"const\": \"response_url\" } } },\n            {\n              \"properties\": {\n                \"errorType\": {\n                  \"type\": \"array\",\n                  \"contains\": { \"const\": \"response_url\" }\n                }\n              }\n            }\n          ]\n        },\n        \"errorCode\": {\n          \"oneOf\": [\n            { \"properties\": { \"errorType\": { \"const\": \"status_code\" } } },\n            {\n              \"properties\": {\n                \"errorType\": {\n                  \"type\": \"array\",\n                  \"contains\": { \"const\": \"status_code\" }\n                }\n              }\n            }\n          ]\n        }\n      },\n      \"allOf\": [\n        {\n          \"if\": {\n            \"anyOf\": [\n              { \"properties\": { \"errorType\": { \"const\": \"message\" } } },\n              {\n                \"properties\": {\n                  \"errorType\": {\n                    \"type\": \"array\",\n                    \"contains\": { \"const\": \"message\" }\n                  }\n                }\n              }\n            ]\n          },\n          \"then\": { \"required\": [\"errorMsg\"] }\n        },\n        {\n          \"if\": {\n            \"anyOf\": [\n              { \"properties\": { \"errorType\": { \"const\": \"response_url\" } } },\n              {\n                \"properties\": {\n                  \"errorType\": {\n                    \"type\": \"array\",\n                    \"contains\": { \"const\": \"response_url\" }\n                  }\n                }\n              }\n            ]\n          },\n          \"then\": { \"required\": [\"errorUrl\"] }\n        }\n      ],\n      \"additionalProperties\": false\n    }\n  },\n  \"additionalProperties\": false,\n  \"$defs\": {\n    \"tag\": { \"type\": \"string\", \"enum\": [\"adult\", \"gaming\"] }\n  }\n}\n"
  },
  {
    "path": "the_big_brother/result.py",
    "content": "\"\"\"The Big Brother Result Module\n\nThis module defines various objects for recording the results of queries.\n\"\"\"\nfrom enum import Enum\n\n\nclass QueryStatus(Enum):\n    \"\"\"Query Status Enumeration.\n\n    Describes status of query about a given username.\n    \"\"\"\n    CLAIMED   = \"Claimed\"   # Username Detected\n    AVAILABLE = \"Available\" # Username Not Detected\n    UNKNOWN   = \"Unknown\"   # Error Occurred While Trying To Detect Username\n    ILLEGAL   = \"Illegal\"   # Username Not Allowable For This Site\n    WAF       = \"WAF\"       # Request blocked by WAF (i.e. Cloudflare)\n\n    def __str__(self):\n        \"\"\"Convert Object To String.\n\n        Keyword Arguments:\n        self                   -- This object.\n\n        Return Value:\n        Nicely formatted string to get information about this object.\n        \"\"\"\n        return self.value\n\nclass QueryResult():\n    \"\"\"Query Result Object.\n\n    Describes result of query about a given username.\n    \"\"\"\n    def __init__(self, username, site_name, site_url_user, status,\n                 query_time=None, context=None):\n        \"\"\"Create Query Result Object.\n\n        Contains information about a specific method of detecting usernames on\n        a given type of web sites.\n\n        Keyword Arguments:\n        self                   -- This object.\n        username               -- String indicating username that query result\n                                  was about.\n        site_name              -- String which identifies site.\n        site_url_user          -- String containing URL for username on site.\n                                  NOTE:  The site may or may not exist:  this\n                                         just indicates what the name would\n                                         be, if it existed.\n        status                 -- Enumeration of type QueryStatus() indicating\n                                  the status of the query.\n        query_time             -- Time (in seconds) required to perform query.\n                                  Default of None.\n        context                -- String indicating any additional context\n                                  about the query.  For example, if there was\n                                  an error, this might indicate the type of\n                                  error that occurred.\n                                  Default of None.\n\n        Return Value:\n        Nothing.\n        \"\"\"\n\n        self.username      = username\n        self.site_name     = site_name\n        self.site_url_user = site_url_user\n        self.status        = status\n        self.query_time    = query_time\n        self.context       = context\n\n        return\n\n    def __str__(self):\n        \"\"\"Convert Object To String.\n\n        Keyword Arguments:\n        self                   -- This object.\n\n        Return Value:\n        Nicely formatted string to get information about this object.\n        \"\"\"\n        status = str(self.status)\n        if self.context is not None:\n            # There is extra context information available about the results.\n            # Append it to the normal response text.\n            status += f\" ({self.context})\"\n\n        return status\n"
  },
  {
    "path": "the_big_brother/reverse_search.py",
    "content": "from playwright.async_api import async_playwright\nimport urllib.parse\nimport asyncio\nimport random\n\nclass ReverseImageSearcher:\n    def __init__(self, headless=True):\n        self.headless = headless\n\n    async def _search_google(self, context, encoded_url):\n        results = []\n        try:\n            print(\"   [+] Scanning Google Images... (Async)\")\n            page = await context.new_page()\n            # Using Google Images instead of Lens, with SafeSearch OFF\n            url = f\"https://www.google.com/searchbyimage?image_url={encoded_url}&safe=off\"\n            await page.goto(url, timeout=30000)\n            \n            # CONSENT HANDLING\n            try:\n                # Try generic \"Reject all\" or \"Accept all\" buttons which cover most EU/US cases\n                if await page.get_by_role(\"button\", name=\"Reject all\").is_visible():\n                    await page.get_by_role(\"button\", name=\"Reject all\").click()\n                elif await page.get_by_role(\"button\", name=\"Accept all\").is_visible():\n                     await page.get_by_role(\"button\", name=\"Accept all\").click()\n            except: pass\n\n            await asyncio.sleep(random.uniform(2.5, 4.0))\n\n            results = await page.evaluate(\"\"\"() => {\n                const imgs = Array.from(document.querySelectorAll('img'));\n                return imgs\n                    .map(img => img.src || img.getAttribute('data-src'))\n                    .filter(src => src && src.startsWith('http') && src.length > 80 && !src.includes('gstatic') && !src.includes('google')) \n                    .slice(0, 5);\n            }\"\"\")\n            print(f\"   [+] Google: Found {len(results)} matches.\")\n            await page.close()\n        except Exception as e:\n            print(f\"   [-] Google Error: {e}\")\n        return results\n\n    async def _search_bing(self, context, encoded_url):\n        results = []\n        try:\n            print(\"   [+] Scanning Bing Visual... (Async)\")\n            page = await context.new_page()\n            url = f\"https://www.bing.com/images/search?view=detailv2&iss=sbi&form=SBIHMP&q=imgurl:{encoded_url}&adlt=off\"\n            await page.goto(url, timeout=30000)\n            \n             # Cookie banner check\n            try:\n                if await page.locator('#bnp_btn_reject').is_visible():\n                    await page.click('#bnp_btn_reject')\n            except: pass\n            \n            await asyncio.sleep(random.uniform(2.0, 3.5))\n\n            results = await page.evaluate(\"\"\"() => {\n                const imgs = Array.from(document.querySelectorAll('img'));\n                return imgs\n                    .map(img => img.src || img.getAttribute('data-src'))\n                    .filter(src => src && src.startsWith('http') && src.length > 50 && !src.includes('bing.com'))\n                    .slice(0, 5);\n            }\"\"\")\n            print(f\"   [+] Bing: Found {len(results)} matches.\")\n            await page.close()\n        except Exception as e:\n            print(f\"   [-] Bing Error: {e}\")\n        return results\n\n    async def _search_yandex(self, context, encoded_url):\n        results = []\n        try:\n            print(\"   [+] Scanning Yandex Visual... (Async)\")\n            page = await context.new_page()\n            url = f\"https://yandex.com/images/search?rpt=imageview&url={encoded_url}\"\n            # Yandex needs retry logic often\n            try:\n                await page.goto(url, timeout=40000)\n            except:\n                await page.reload()\n            \n            await asyncio.sleep(random.uniform(3.0, 5.0))\n\n            results = await page.evaluate(\"\"\"() => {\n                const imgs = Array.from(document.querySelectorAll('.serp-item__thumb, img.serp-item__img, .CbirSites-ItemThumb'));\n                return imgs\n                        .map(img => img.src || img.getAttribute('data-src'))\n                        .filter(src => src && src.startsWith('http') && src.length > 50)\n                        .slice(0, 5);\n            }\"\"\")\n            print(f\"   [+] Yandex: Found {len(results)} matches.\")\n            await page.close()\n        except Exception as e:\n            print(f\"   [-] Yandex Error: {e}\")\n        return results\n\n    async def _search_tineye(self, context, encoded_url):\n        results = []\n        try:\n            print(\"   [+] Scanning TinEye... (Async)\")\n            page = await context.new_page()\n            url = f\"https://tineye.com/search?url={encoded_url}\"\n            await page.goto(url, timeout=30000)\n            \n            await asyncio.sleep(random.uniform(2.0, 4.0))\n\n            results = await page.evaluate(\"\"\"() => {\n                 // TinEye results are usually in .match div with .match-thumb img\n                const imgs = Array.from(document.querySelectorAll('.match-thumb img, .result-match img'));\n                return imgs\n                    .map(img => img.src)\n                    .filter(src => src && src.startsWith('http'))\n                    .slice(0, 5);\n            }\"\"\")\n            print(f\"   [+] TinEye: Found {len(results)} matches.\")\n            await page.close()\n        except Exception as e:\n            print(f\"   [-] TinEye Error: {e}\")\n        return results\n\n    async def search(self, image_url: str) -> dict:\n        results = {\"google\": [], \"bing\": [], \"yandex\": [], \"tineye\": []}\n        encoded_url = urllib.parse.quote(image_url)\n        print(f\"[*] Starting Async Quad-Vector Search for: {image_url}\")\n\n        async with async_playwright() as p:\n            browser = await p.chromium.launch(\n                headless=self.headless,\n                args=['--disable-blink-features=AutomationControlled']\n            )\n            \n            ctx_g = await browser.new_context(viewport={\"width\":1920,\"height\":1080}, user_agent=\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\")\n            ctx_b = await browser.new_context(viewport={\"width\":1920,\"height\":1080}, user_agent=\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15\")\n            ctx_y = await browser.new_context(viewport={\"width\":1920,\"height\":1080}, user_agent=\"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\")\n            ctx_t = await browser.new_context(viewport={\"width\":1920,\"height\":1080}, user_agent=\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\")\n\n            # Run in parallel\n            g_res, b_res, y_res, t_res = await asyncio.gather(\n                self._search_google(ctx_g, encoded_url),\n                self._search_bing(ctx_b, encoded_url),\n                self._search_yandex(ctx_y, encoded_url),\n                self._search_tineye(ctx_t, encoded_url)\n            )\n            \n            results[\"google\"] = g_res\n            results[\"bing\"] = b_res\n            results[\"yandex\"] = y_res\n            results[\"tineye\"] = t_res\n            \n            await browser.close()\n            \n        print(\"[*] Deep Search Complete.\")\n        return results\n"
  },
  {
    "path": "the_big_brother/scanner.py",
    "content": "#! /usr/bin/env python3\n\n\"\"\"\nFind Usernames Across Social Networks Module\n\nThis module contains the main logic to search for usernames at social\nnetworks.\n\"\"\"\n\nimport sys\n\ntry:\n    from the_big_brother.__init__ import import_error_test_var # noqa: F401\nexcept ImportError:\n    print(\"Did you run The Big Brother with `python3 the_big_brother/scanner.py ...`?\")\n    print(\"This is an outdated method. Please use the installed package or run as module.\")\n    sys.exit(1)\n\nimport csv\nimport signal\nimport pandas as pd\nimport os\nimport re\nfrom argparse import ArgumentParser, RawDescriptionHelpFormatter\nfrom json import loads as json_loads\nfrom time import monotonic, sleep\nfrom typing import Optional, Union\n\nimport requests\nfrom requests_futures.sessions import FuturesSession\n\nfrom the_big_brother.__init__ import (\n    __longname__,\n    __shortname__,\n    __version__,\n    forge_api_latest_release,\n)\n\nfrom the_big_brother.result import QueryStatus\nfrom the_big_brother.result import QueryResult\nfrom the_big_brother.notify import QueryNotify\nfrom the_big_brother.notify import QueryNotifyPrint\nfrom the_big_brother.sites import SitesInformation\nfrom colorama import init\nfrom argparse import ArgumentTypeError\n\n\nclass BigBrotherFuturesSession(FuturesSession):\n    def request(self, method, url, hooks=None, *args, **kwargs):\n        \"\"\"Request URL.\n\n        This extends the FuturesSession request method to calculate a response\n        time metric to each request.\n\n        It is taken (almost) directly from the following Stack Overflow answer:\n        https://github.com/ross/requests-futures#working-in-the-background\n\n        Keyword Arguments:\n        self                   -- This object.\n        method                 -- String containing method desired for request.\n        url                    -- String containing URL for request.\n        hooks                  -- Dictionary containing hooks to execute after\n                                  request finishes.\n        args                   -- Arguments.\n        kwargs                 -- Keyword arguments.\n\n        Return Value:\n        Request object.\n        \"\"\"\n        # Record the start time for the request.\n        if hooks is None:\n            hooks = {}\n        start = monotonic()\n\n        def response_time(resp, *args, **kwargs):\n            \"\"\"Response Time Hook.\n\n            Keyword Arguments:\n            resp                   -- Response object.\n            args                   -- Arguments.\n            kwargs                 -- Keyword arguments.\n\n            Return Value:\n            Nothing.\n            \"\"\"\n            resp.elapsed = monotonic() - start\n\n            return\n\n        # Install hook to execute when response completes.\n        # Make sure that the time measurement hook is first, so we will not\n        # track any later hook's execution time.\n        try:\n            if isinstance(hooks[\"response\"], list):\n                hooks[\"response\"].insert(0, response_time)\n            elif isinstance(hooks[\"response\"], tuple):\n                # Convert tuple to list and insert time measurement hook first.\n                hooks[\"response\"] = list(hooks[\"response\"])\n                hooks[\"response\"].insert(0, response_time)\n            else:\n                # Must have previously contained a single hook function,\n                # so convert to list.\n                hooks[\"response\"] = [response_time, hooks[\"response\"]]\n        except KeyError:\n            # No response hook was already defined, so install it ourselves.\n            hooks[\"response\"] = [response_time]\n\n        return super(BigBrotherFuturesSession, self).request(\n            method, url, hooks=hooks, *args, **kwargs\n        )\n\n\ndef get_response(request_future, error_type, social_network):\n    # Default for Response object if some failure occurs.\n    response = None\n\n    error_context = \"General Unknown Error\"\n    exception_text = None\n    try:\n        response = request_future.result()\n        if response.status_code:\n            # Status code exists in response object\n            error_context = None\n    except requests.exceptions.HTTPError as errh:\n        error_context = \"HTTP Error\"\n        exception_text = str(errh)\n    except requests.exceptions.ProxyError as errp:\n        error_context = \"Proxy Error\"\n        exception_text = str(errp)\n    except requests.exceptions.ConnectionError as errc:\n        error_context = \"Error Connecting\"\n        exception_text = str(errc)\n    except requests.exceptions.Timeout as errt:\n        error_context = \"Timeout Error\"\n        exception_text = str(errt)\n    except requests.exceptions.RequestException as err:\n        error_context = \"Unknown Error\"\n        exception_text = str(err)\n\n    return response, error_context, exception_text\n\n\ndef interpolate_string(input_object, username):\n    if isinstance(input_object, str):\n        return input_object.replace(\"{}\", username)\n    elif isinstance(input_object, dict):\n        return {k: interpolate_string(v, username) for k, v in input_object.items()}\n    elif isinstance(input_object, list):\n        return [interpolate_string(i, username) for i in input_object]\n    return input_object\n\n\ndef check_for_parameter(username):\n    \"\"\"checks if {?} exists in the username\n    if exist it means that scanner is looking for more multiple username\"\"\"\n    return \"{?}\" in username\n\n\nchecksymbols = [\"_\", \"-\", \".\"]\n\n\ndef multiple_usernames(username):\n    \"\"\"replace the parameter with with symbols and return a list of usernames\"\"\"\n    allUsernames = []\n    for i in checksymbols:\n        allUsernames.append(username.replace(\"{?}\", i))\n    return allUsernames\n\n\ndef scan(\n    username: str,\n    site_data: dict[str, dict[str, str]],\n    query_notify: QueryNotify,\n    dump_response: bool = False,\n    proxy: Optional[str] = None,\n    timeout: int = 60,\n) -> dict[str, dict[str, Union[str, QueryResult]]]:\n    \"\"\"Run The Big Brother Analysis.\n\n    Checks for existence of username on various social media sites.\n\n    Keyword Arguments:\n    username               -- String indicating username that report\n                              should be created against.\n    site_data              -- Dictionary containing all of the site data.\n    query_notify           -- Object with base type of QueryNotify().\n                              This will be used to notify the caller about\n                              query results.\n    proxy                  -- String indicating the proxy URL\n    timeout                -- Time in seconds to wait before timing out request.\n                              Default is 60 seconds.\n\n    Return Value:\n    Dictionary containing results from report. Key of dictionary is the name\n    of the social network site, and the value is another dictionary with\n    the following keys:\n        url_main:      URL of main site.\n        url_user:      URL of user on site (if account exists).\n        status:        QueryResult() object indicating results of test for\n                       account existence.\n        http_status:   HTTP status code of query which checked for existence on\n                       site.\n        response_text: Text that came back from request.  May be None if\n                       there was an HTTP error when checking for existence.\n    \"\"\"\n\n    # Notify caller that we are starting the query.\n    query_notify.start(username)\n\n    # Normal requests\n    underlying_session = requests.session()\n\n    # Limit number of workers to 20.\n    # This is probably vastly overkill.\n    if len(site_data) >= 20:\n        max_workers = 20\n    else:\n        max_workers = len(site_data)\n\n    # Create multi-threaded session for all requests.\n    session = BigBrotherFuturesSession(\n        max_workers=max_workers, session=underlying_session\n    )\n\n    # Results from analysis of all sites\n    results_total = {}\n\n    # First create futures for all requests. This allows for the requests to run in parallel\n    for social_network, net_info in site_data.items():\n        # Results from analysis of this specific site\n        results_site = {\"url_main\": net_info.get(\"urlMain\")}\n\n        # Record URL of main site\n\n        # A user agent is needed because some sites don't return the correct\n        # information since they think that we are bots (Which we actually are...)\n        headers = {\n            \"User-Agent\": \"Mozilla/5.0 (X11; Linux x86_64; rv:129.0) Gecko/20100101 Firefox/129.0\",\n        }\n\n        if \"headers\" in net_info:\n            # Override/append any extra headers required by a given site.\n            headers.update(net_info[\"headers\"])\n\n        # URL of user on site (if it exists)\n        url = interpolate_string(net_info[\"url\"], username.replace(' ', '%20'))\n\n        # Don't make request if username is invalid for the site\n        regex_check = net_info.get(\"regexCheck\")\n        if regex_check and re.search(regex_check, username) is None:\n            # No need to do the check at the site: this username is not allowed.\n            results_site[\"status\"] = QueryResult(\n                username, social_network, url, QueryStatus.ILLEGAL\n            )\n            results_site[\"url_user\"] = \"\"\n            results_site[\"http_status\"] = \"\"\n            results_site[\"response_text\"] = \"\"\n            query_notify.update(results_site[\"status\"])\n        else:\n            # URL of user on site (if it exists)\n            results_site[\"url_user\"] = url\n            url_probe = net_info.get(\"urlProbe\")\n            request_method = net_info.get(\"request_method\")\n            request_payload = net_info.get(\"request_payload\")\n            request = None\n\n            if request_method is not None:\n                if request_method == \"GET\":\n                    request = session.get\n                elif request_method == \"HEAD\":\n                    request = session.head\n                elif request_method == \"POST\":\n                    request = session.post\n                elif request_method == \"PUT\":\n                    request = session.put\n                else:\n                    raise RuntimeError(f\"Unsupported request_method for {url}\")\n\n            if request_payload is not None:\n                request_payload = interpolate_string(request_payload, username)\n\n            if url_probe is None:\n                # Probe URL is normal one seen by people out on the web.\n                url_probe = url\n            else:\n                # There is a special URL for probing existence separate\n                # from where the user profile normally can be found.\n                url_probe = interpolate_string(url_probe, username)\n\n            if request is None:\n                if net_info[\"errorType\"] == \"status_code\":\n                    # In most cases when we are detecting by status code,\n                    # it is not necessary to get the entire body:  we can\n                    # detect fine with just the HEAD response.\n                    request = session.head\n                else:\n                    # Either this detect method needs the content associated\n                    # with the GET response, or this specific website will\n                    # not respond properly unless we request the whole page.\n                    request = session.get\n            \n            # Add delay to prevent rate limiting (reduced for speed)\n            sleep(0.1)\n\n            if net_info[\"errorType\"] == \"response_url\":\n                # Site forwards request to a different URL if username not\n                # found.  Disallow the redirect so we can capture the\n                # http status from the original URL request.\n                allow_redirects = False\n            else:\n                # Allow whatever redirect that the site wants to do.\n                # The final result of the request will be what is available.\n                allow_redirects = True\n\n            # This future starts running the request in a new thread, doesn't block the main thread\n            if proxy is not None:\n                proxies = {\"http\": proxy, \"https\": proxy}\n                future = request(\n                    url=url_probe,\n                    headers=headers,\n                    proxies=proxies,\n                    allow_redirects=allow_redirects,\n                    timeout=timeout,\n                    json=request_payload,\n                )\n            else:\n                future = request(\n                    url=url_probe,\n                    headers=headers,\n                    allow_redirects=allow_redirects,\n                    timeout=timeout,\n                    json=request_payload,\n                )\n\n            # Store future in data for access later\n            net_info[\"request_future\"] = future\n\n        # Add this site's results into final dictionary with all the other results.\n        results_total[social_network] = results_site\n\n    # Open the file containing account links\n    for social_network, net_info in site_data.items():\n        # Retrieve results again\n        results_site = results_total.get(social_network)\n\n        # Retrieve other site information again\n        url = results_site.get(\"url_user\")\n        status = results_site.get(\"status\")\n        if status is not None:\n            # We have already determined the user doesn't exist here\n            continue\n\n        # Get the expected error type\n        error_type = net_info[\"errorType\"]\n        if isinstance(error_type, str):\n            error_type: list[str] = [error_type]\n\n        # Retrieve future and ensure it has finished\n        future = net_info[\"request_future\"]\n        r, error_text, exception_text = get_response(\n            request_future=future, error_type=error_type, social_network=social_network\n        )\n\n        # Get response time for response of our request.\n        try:\n            response_time = r.elapsed\n        except AttributeError:\n            response_time = None\n\n        # Attempt to get request information\n        try:\n            http_status = r.status_code\n        except Exception:\n            http_status = \"?\"\n        try:\n            response_text = r.text.encode(r.encoding or \"UTF-8\")\n        except Exception:\n            response_text = \"\"\n\n        query_status = QueryStatus.UNKNOWN\n        error_context = None\n\n        # As WAFs advance and evolve, they will occasionally block The Big Brother and\n        # lead to false positives and negatives. Fingerprints should be added\n        # here to filter results that fail to bypass WAFs. Fingerprints should\n        # be highly targetted. Comment at the end of each fingerprint to\n        # indicate target and date fingerprinted.\n        WAFHitMsgs = [\n            r'.loading-spinner{visibility:hidden}body.no-js .challenge-running{display:none}body.dark{background-color:#222;color:#d9d9d9}body.dark a{color:#fff}body.dark a:hover{color:#ee730a;text-decoration:underline}body.dark .lds-ring div{border-color:#999 transparent transparent}body.dark .font-red{color:#b20f03}body.dark', # 2024-05-13 Cloudflare\n            r'<span id=\"challenge-error-text\">', # 2024-11-11 Cloudflare error page\n            r'AwsWafIntegration.forceRefreshToken', # 2024-11-11 Cloudfront (AWS)\n            r'{return l.onPageView}}),Object.defineProperty(r,\"perimeterxIdentifiers\",{enumerable:' # 2024-04-09 PerimeterX / Human Security\n        ]\n\n        if error_text is not None:\n            error_context = error_text\n\n        elif any(hitMsg in r.text for hitMsg in WAFHitMsgs):\n            query_status = QueryStatus.WAF\n\n        else:\n            if any(errtype not in [\"message\", \"status_code\", \"response_url\"] for errtype in error_type):\n                error_context = f\"Unknown error type '{error_type}' for {social_network}\"\n                query_status = QueryStatus.UNKNOWN\n            else:\n                if \"message\" in error_type:\n                    # error_flag True denotes no error found in the HTML\n                    # error_flag False denotes error found in the HTML\n                    error_flag = True\n                    errors = net_info.get(\"errorMsg\")\n                    # errors will hold the error message\n                    # it can be string or list\n                    # by isinstance method we can detect that\n                    # and handle the case for strings as normal procedure\n                    # and if its list we can iterate the errors\n                    if isinstance(errors, str):\n                        # Checks if the error message is in the HTML\n                        # if error is present we will set flag to False\n                        if errors in r.text:\n                            error_flag = False\n                    else:\n                        # If it's list, it will iterate all the error message\n                        for error in errors:\n                            if error in r.text:\n                                error_flag = False\n                                break\n                    if error_flag:\n                        query_status = QueryStatus.CLAIMED\n                    else:\n                        query_status = QueryStatus.AVAILABLE\n\n                if \"status_code\" in error_type and query_status is not QueryStatus.AVAILABLE:\n                    error_codes = net_info.get(\"errorCode\")\n                    query_status = QueryStatus.CLAIMED\n\n                    # Type consistency, allowing for both singlets and lists in manifest\n                    if isinstance(error_codes, int):\n                        error_codes = [error_codes]\n\n                    if error_codes is not None and r.status_code in error_codes:\n                        query_status = QueryStatus.AVAILABLE\n                    elif r.status_code >= 300 or r.status_code < 200:\n                        query_status = QueryStatus.AVAILABLE\n\n                if \"response_url\" in error_type and query_status is not QueryStatus.AVAILABLE:\n                    # For this detection method, we have turned off the redirect.\n                    # So, there is no need to check the response URL: it will always\n                    # match the request.  Instead, we will ensure that the response\n                    # code indicates that the request was successful (i.e. no 404, or\n                    # forward to some odd redirect).\n                    if 200 <= r.status_code < 300:\n                        query_status = QueryStatus.CLAIMED\n                    else:\n                        query_status = QueryStatus.AVAILABLE\n\n        if dump_response:\n            print(\"+++++++++++++++++++++\")\n            print(f\"TARGET NAME   : {social_network}\")\n            print(f\"USERNAME      : {username}\")\n            print(f\"TARGET URL    : {url}\")\n            print(f\"TEST METHOD   : {error_type}\")\n            try:\n                print(f\"STATUS CODES  : {net_info['errorCode']}\")\n            except KeyError:\n                pass\n            print(\"Results...\")\n            try:\n                print(f\"RESPONSE CODE : {r.status_code}\")\n            except Exception:\n                pass\n            try:\n                print(f\"ERROR TEXT    : {net_info['errorMsg']}\")\n            except KeyError:\n                pass\n            print(\">>>>> BEGIN RESPONSE TEXT\")\n            try:\n                print(r.text)\n            except Exception:\n                pass\n            print(\"<<<<< END RESPONSE TEXT\")\n            print(\"VERDICT       : \" + str(query_status))\n            print(\"+++++++++++++++++++++\")\n\n        # Notify caller about results of query.\n        result: QueryResult = QueryResult(\n            username=username,\n            site_name=social_network,\n            site_url_user=url,\n            status=query_status,\n            query_time=response_time,\n            context=error_context,\n        )\n        query_notify.update(result)\n\n        # Save status of request\n        results_site[\"status\"] = result\n\n        # Save results from request\n        results_site[\"http_status\"] = http_status\n        results_site[\"response_text\"] = response_text\n\n        # Add this site's results into final dictionary with all of the other results.\n        results_total[social_network] = results_site\n\n    return results_total\n\n\ndef timeout_check(value):\n    \"\"\"Check Timeout Argument.\n\n    Checks timeout for validity.\n\n    Keyword Arguments:\n    value                  -- Time in seconds to wait before timing out request.\n\n    Return Value:\n    Floating point number representing the time (in seconds) that should be\n    used for the timeout.\n\n    NOTE:  Will raise an exception if the timeout in invalid.\n    \"\"\"\n\n    float_value = float(value)\n\n    if float_value <= 0:\n        raise ArgumentTypeError(\n            f\"Invalid timeout value: {value}. Timeout must be a positive number.\"\n        )\n\n    return float_value\n\n\ndef handler(signal_received, frame):\n    \"\"\"Exit gracefully without throwing errors\n\n    Source: https://www.devdungeon.com/content/python-catch-sigint-ctrl-c\n    \"\"\"\n    sys.exit(0)\n\n\ndef main():\n    parser = ArgumentParser(\n        formatter_class=RawDescriptionHelpFormatter,\n        description=f\"{__longname__} (Version {__version__})\",\n    )\n    parser.add_argument(\n        \"--version\",\n        action=\"version\",\n        version=f\"{__shortname__} v{__version__}\",\n        help=\"Display version information and dependencies.\",\n    )\n    parser.add_argument(\n        \"--verbose\",\n        \"-v\",\n        \"-d\",\n        \"--debug\",\n        action=\"store_true\",\n        dest=\"verbose\",\n        default=False,\n        help=\"Display extra debugging information and metrics.\",\n    )\n    parser.add_argument(\n        \"--folderoutput\",\n        \"-fo\",\n        dest=\"folderoutput\",\n        help=\"If using multiple usernames, the output of the results will be saved to this folder.\",\n    )\n    parser.add_argument(\n        \"--output\",\n        \"-o\",\n        dest=\"output\",\n        help=\"If using single username, the output of the result will be saved to this file.\",\n    )\n    parser.add_argument(\n        \"--csv\",\n        action=\"store_true\",\n        dest=\"csv\",\n        default=False,\n        help=\"Create Comma-Separated Values (CSV) File.\",\n    )\n    parser.add_argument(\n        \"--xlsx\",\n        action=\"store_true\",\n        dest=\"xlsx\",\n        default=False,\n        help=\"Create the standard file for the modern Microsoft Excel spreadsheet (xlsx).\",\n    )\n    parser.add_argument(\n        \"--site\",\n        action=\"append\",\n        metavar=\"SITE_NAME\",\n        dest=\"site_list\",\n        default=[],\n        help=\"Limit analysis to just the listed sites. Add multiple options to specify more than one site.\",\n    )\n    parser.add_argument(\n        \"--proxy\",\n        \"-p\",\n        metavar=\"PROXY_URL\",\n        action=\"store\",\n        dest=\"proxy\",\n        default=None,\n        help=\"Make requests over a proxy. e.g. socks5://127.0.0.1:1080\",\n    )\n    parser.add_argument(\n        \"--dump-response\",\n        action=\"store_true\",\n        dest=\"dump_response\",\n        default=False,\n        help=\"Dump the HTTP response to stdout for targeted debugging.\",\n    )\n    parser.add_argument(\n        \"--json\",\n        \"-j\",\n        metavar=\"JSON_FILE\",\n        dest=\"json_file\",\n        default=None,\n        help=\"Load data from a JSON file or an online, valid, JSON file. Upstream PR numbers also accepted.\",\n    )\n    parser.add_argument(\n        \"--timeout\",\n        action=\"store\",\n        metavar=\"TIMEOUT\",\n        dest=\"timeout\",\n        type=timeout_check,\n        default=60,\n        help=\"Time (in seconds) to wait for response to requests (Default: 60)\",\n    )\n    parser.add_argument(\n        \"--print-all\",\n        action=\"store_true\",\n        dest=\"print_all\",\n        default=False,\n        help=\"Output sites where the username was not found.\",\n    )\n    parser.add_argument(\n        \"--print-found\",\n        action=\"store_true\",\n        dest=\"print_found\",\n        default=True,\n        help=\"Output sites where the username was found (also if exported as file).\",\n    )\n    parser.add_argument(\n        \"--no-color\",\n        action=\"store_true\",\n        dest=\"no_color\",\n        default=False,\n        help=\"Don't color terminal output\",\n    )\n    parser.add_argument(\n        \"username\",\n        nargs=\"+\",\n        metavar=\"USERNAMES\",\n        action=\"store\",\n        help=\"One or more usernames to check with social networks. Check similar usernames using {?} (replace to '_', '-', '.').\",\n    )\n    parser.add_argument(\n        \"--browse\",\n        \"-b\",\n        action=\"store_true\",\n        dest=\"browse\",\n        default=False,\n        help=\"Browse to all results on default browser.\",\n    )\n\n    parser.add_argument(\n        \"--local\",\n        \"-l\",\n        action=\"store_true\",\n        default=False,\n        help=\"Force the use of the local data.json file.\",\n    )\n\n    parser.add_argument(\n        \"--nsfw\",\n        action=\"store_true\",\n        default=False,\n        help=\"Include checking of NSFW sites from default list.\",\n    )\n\n    # TODO deprecated in favor of --txt, retained for workflow compatibility, to be removed\n    # in future release\n    parser.add_argument(\n        \"--no-txt\",\n        action=\"store_true\",\n        dest=\"no_txt\",\n        default=False,\n        help=\"Disable creation of a txt file - WILL BE DEPRECATED\",\n    )\n\n    parser.add_argument(\n        \"--txt\",\n        action=\"store_true\",\n        dest=\"output_txt\",\n        default=False,\n        help=\"Enable creation of a txt file\",\n    )\n\n    parser.add_argument(\n        \"--ignore-exclusions\",\n        action=\"store_true\",\n        dest=\"ignore_exclusions\",\n        default=False,\n        help=\"Ignore upstream exclusions (may return more false positives)\",\n    )\n\n    args = parser.parse_args()\n\n    # If the user presses CTRL-C, exit gracefully without throwing errors\n    signal.signal(signal.SIGINT, handler)\n\n    # Check for newer version of Sherlock. If it exists, let the user know about it\n    try:\n        latest_release_raw = requests.get(forge_api_latest_release, timeout=10).text\n        latest_release_json = json_loads(latest_release_raw)\n        latest_remote_tag = latest_release_json[\"tag_name\"]\n\n        # if latest_remote_tag[1:] != __version__:\n        #     print(\n        #         f\"Update available! {__version__} --> {latest_remote_tag[1:]}\"\n        #         f\"\\n{latest_release_json['html_url']}\"\n        #     )\n        pass # Update check disabled\n\n    except Exception as error:\n        print(f\"A problem occurred while checking for an update: {error}\")\n\n    # Make prompts\n    if args.proxy is not None:\n        print(\"Using the proxy: \" + args.proxy)\n\n    if args.no_color:\n        # Disable color output.\n        init(strip=True, convert=False)\n    else:\n        # Enable color output.\n        init(autoreset=True)\n\n    # Check if both output methods are entered as input.\n    if args.output is not None and args.folderoutput is not None:\n        print(\"You can only use one of the output methods.\")\n        sys.exit(1)\n\n    # Check validity for single username output.\n    if args.output is not None and len(args.username) != 1:\n        print(\"You can only use --output with a single username\")\n        sys.exit(1)\n\n    # Create object with all information about sites we are aware of.\n    try:\n        if args.local:\n            sites = SitesInformation(\n                os.path.join(os.path.dirname(__file__), \"resources/data.json\"),\n                honor_exclusions=False,\n            )\n        else:\n            json_file_location = args.json_file\n            if args.json_file:\n                # If --json parameter is a number, interpret it as a pull request number\n                if args.json_file.isnumeric():\n                    pull_number = args.json_file\n                    pull_url = f\"https://api.github.com/repos/sherlock-project/sherlock/pulls/{pull_number}\"\n                    pull_request_raw = requests.get(pull_url, timeout=10).text\n                    pull_request_json = json_loads(pull_request_raw)\n\n                    # Check if it's a valid pull request\n                    if \"message\" in pull_request_json:\n                        print(f\"ERROR: Pull request #{pull_number} not found.\")\n                        sys.exit(1)\n\n                    head_commit_sha = pull_request_json[\"head\"][\"sha\"]\n                    json_file_location = f\"https://raw.githubusercontent.com/sherlock-project/sherlock/{head_commit_sha}/sherlock_project/resources/data.json\"\n\n            sites = SitesInformation(\n                data_file_path=json_file_location,\n                honor_exclusions=not args.ignore_exclusions,\n                do_not_exclude=args.site_list,\n            )\n    except Exception as error:\n        print(f\"ERROR:  {error}\")\n        sys.exit(1)\n\n    if not args.nsfw:\n        sites.remove_nsfw_sites(do_not_remove=args.site_list)\n\n    # Create original dictionary from SitesInformation() object.\n    # Eventually, the rest of the code will be updated to use the new object\n    # directly, but this will glue the two pieces together.\n    site_data_all = {site.name: site.information for site in sites}\n    if args.site_list == []:\n        # Not desired to look at a sub-set of sites\n        site_data = site_data_all\n    else:\n        # User desires to selectively run queries on a sub-set of the site list.\n        # Make sure that the sites are supported & build up pruned site database.\n        site_data = {}\n        site_missing = []\n        for site in args.site_list:\n            counter = 0\n            for existing_site in site_data_all:\n                if site.lower() == existing_site.lower():\n                    site_data[existing_site] = site_data_all[existing_site]\n                    counter += 1\n            if counter == 0:\n                # Build up list of sites not supported for future error message.\n                site_missing.append(f\"'{site}'\")\n\n        if site_missing:\n            print(f\"Error: Desired sites not found: {', '.join(site_missing)}.\")\n\n        if not site_data:\n            sys.exit(1)\n\n    # Create notify object for query results.\n    query_notify = QueryNotifyPrint(\n        result=None, verbose=args.verbose, print_all=args.print_all, browse=args.browse\n    )\n\n    # Run report on all specified users.\n    all_usernames = []\n    for username in args.username:\n        if check_for_parameter(username):\n            for name in multiple_usernames(username):\n                all_usernames.append(name)\n        else:\n            all_usernames.append(username)\n    for username in all_usernames:\n        results = scan(\n            username,\n            site_data,\n            query_notify,\n            dump_response=args.dump_response,\n            proxy=args.proxy,\n            timeout=args.timeout,\n        )\n\n        if args.output:\n            result_file = args.output\n        elif args.folderoutput:\n            # The usernames results should be stored in a targeted folder.\n            # If the folder doesn't exist, create it first\n            os.makedirs(args.folderoutput, exist_ok=True)\n            result_file = os.path.join(args.folderoutput, f\"{username}.txt\")\n        else:\n            result_file = f\"{username}.txt\"\n\n        if args.output_txt:\n            with open(result_file, \"w\", encoding=\"utf-8\") as file:\n                exists_counter = 0\n                for website_name in results:\n                    dictionary = results[website_name]\n                    if dictionary.get(\"status\").status == QueryStatus.CLAIMED:\n                        exists_counter += 1\n                        file.write(dictionary[\"url_user\"] + \"\\n\")\n                file.write(f\"Total Websites Username Detected On : {exists_counter}\\n\")\n\n        if args.csv:\n            result_file = f\"{username}.csv\"\n            if args.folderoutput:\n                # The usernames results should be stored in a targeted folder.\n                # If the folder doesn't exist, create it first\n                os.makedirs(args.folderoutput, exist_ok=True)\n                result_file = os.path.join(args.folderoutput, result_file)\n\n            with open(result_file, \"w\", newline=\"\", encoding=\"utf-8\") as csv_report:\n                writer = csv.writer(csv_report)\n                writer.writerow(\n                    [\n                        \"username\",\n                        \"name\",\n                        \"url_main\",\n                        \"url_user\",\n                        \"exists\",\n                        \"http_status\",\n                        \"response_time_s\",\n                    ]\n                )\n                for site in results:\n                    if (\n                        args.print_found\n                        and not args.print_all\n                        and results[site][\"status\"].status != QueryStatus.CLAIMED\n                    ):\n                        continue\n\n                    response_time_s = results[site][\"status\"].query_time\n                    if response_time_s is None:\n                        response_time_s = \"\"\n                    writer.writerow(\n                        [\n                            username,\n                            site,\n                            results[site][\"url_main\"],\n                            results[site][\"url_user\"],\n                            str(results[site][\"status\"].status),\n                            results[site][\"http_status\"],\n                            response_time_s,\n                        ]\n                    )\n        if args.xlsx:\n            usernames = []\n            names = []\n            url_main = []\n            url_user = []\n            exists = []\n            http_status = []\n            response_time_s = []\n\n            for site in results:\n                if (\n                    args.print_found\n                    and not args.print_all\n                    and results[site][\"status\"].status != QueryStatus.CLAIMED\n                ):\n                    continue\n\n                if response_time_s is None:\n                    response_time_s.append(\"\")\n                else:\n                    response_time_s.append(results[site][\"status\"].query_time)\n                usernames.append(username)\n                names.append(site)\n                url_main.append(results[site][\"url_main\"])\n                url_user.append(results[site][\"url_user\"])\n                exists.append(str(results[site][\"status\"].status))\n                http_status.append(results[site][\"http_status\"])\n\n            DataFrame = pd.DataFrame(\n                {\n                    \"username\": usernames,\n                    \"name\": names,\n                    \"url_main\": [f'=HYPERLINK(\\\"{u}\\\")' for u in url_main],\n                    \"url_user\": [f'=HYPERLINK(\\\"{u}\\\")' for u in url_user],\n                    \"exists\": exists,\n                    \"http_status\": http_status,\n                    \"response_time_s\": response_time_s,\n                }\n            )\n            DataFrame.to_excel(f\"{username}.xlsx\", sheet_name=\"sheet1\", index=False)\n\n        print()\n    query_notify.finish()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "the_big_brother/sites.py",
    "content": "\"\"\"The Big Brother Sites Information Module\n\nThis module supports storing information about websites.\nThis is the raw data that will be used to search for usernames.\n\"\"\"\nimport json\nimport requests\nimport secrets\n\n\nMANIFEST_URL = \"https://raw.githubusercontent.com/sherlock-project/sherlock/master/sherlock_project/resources/data.json\"\nEXCLUSIONS_URL = \"https://raw.githubusercontent.com/sherlock-project/sherlock/refs/heads/exclusions/false_positive_exclusions.txt\"\n\nclass SiteInformation:\n    def __init__(self, name, url_home, url_username_format, username_claimed,\n                information, is_nsfw, username_unclaimed=secrets.token_urlsafe(10)):\n        \"\"\"Create Site Information Object.\n\n        Contains information about a specific website.\n\n        Keyword Arguments:\n        self                   -- This object.\n        name                   -- String which identifies site.\n        url_home               -- String containing URL for home of site.\n        url_username_format    -- String containing URL for Username format\n                                  on site.\n                                  NOTE:  The string should contain the\n                                         token \"{}\" where the username should\n                                         be substituted.  For example, a string\n                                         of \"https://somesite.com/users/{}\"\n                                         indicates that the individual\n                                         usernames would show up under the\n                                         \"https://somesite.com/users/\" area of\n                                         the website.\n        username_claimed       -- String containing username which is known\n                                  to be claimed on website.\n        username_unclaimed     -- String containing username which is known\n                                  to be unclaimed on website.\n        information            -- Dictionary containing all known information\n                                  about website.\n                                  NOTE:  Custom information about how to\n                                         actually detect the existence of the\n                                         username will be included in this\n                                         dictionary.  This information will\n                                         be needed by the detection method,\n                                         but it is only recorded in this\n                                         object for future use.\n        is_nsfw                -- Boolean indicating if site is Not Safe For Work.\n\n        Return Value:\n        Nothing.\n        \"\"\"\n\n        self.name = name\n        self.url_home = url_home\n        self.url_username_format = url_username_format\n\n        self.username_claimed = username_claimed\n        self.username_unclaimed = secrets.token_urlsafe(32)\n        self.information = information\n        self.is_nsfw  = is_nsfw\n\n        return\n\n    def __str__(self):\n        \"\"\"Convert Object To String.\n\n        Keyword Arguments:\n        self                   -- This object.\n\n        Return Value:\n        Nicely formatted string to get information about this object.\n        \"\"\"\n\n        return f\"{self.name} ({self.url_home})\"\n\n\nfrom typing import Optional\n\nclass SitesInformation:\n    def __init__(\n            self,\n            data_file_path: Optional[str] = None,\n            honor_exclusions: bool = True,\n            do_not_exclude: list[str] = [],\n        ):\n        \"\"\"Create Sites Information Object.\n\n        Contains information about all supported websites.\n\n        Keyword Arguments:\n        self                   -- This object.\n        data_file_path         -- String which indicates path to data file.\n                                  The file name must end in \".json\".\n\n                                  There are 3 possible formats:\n                                   * Absolute File Format\n                                     For example, \"c:/stuff/data.json\".\n                                   * Relative File Format\n                                     The current working directory is used\n                                     as the context.\n                                     For example, \"data.json\".\n                                   * URL Format\n                                     For example,\n                                     \"https://example.com/data.json\", or\n                                     \"http://example.com/data.json\".\n\n                                  An exception will be thrown if the path\n                                  to the data file is not in the expected\n                                  format, or if there was any problem loading\n                                  the file.\n\n                                  If this option is not specified, then a\n                                  default site list will be used.\n\n        Return Value:\n        Nothing.\n        \"\"\"\n\n        if not data_file_path:\n            # The default data file is the live data.json which is in the GitHub repo. The reason why we are using\n            # this instead of the local one is so that the user has the most up-to-date data. This prevents\n            # users from creating issue about false positives which has already been fixed or having outdated data\n            data_file_path = MANIFEST_URL\n\n        # Ensure that specified data file has correct extension.\n        if not data_file_path.lower().endswith(\".json\"):\n            raise FileNotFoundError(f\"Incorrect JSON file extension for data file '{data_file_path}'.\")\n\n        # if \"http://\"  == data_file_path[:7].lower() or \"https://\" == data_file_path[:8].lower():\n        if data_file_path.lower().startswith(\"http\"):\n            # Reference is to a URL.\n            try:\n                response = requests.get(url=data_file_path, timeout=30)\n            except Exception as error:\n                raise FileNotFoundError(\n                    f\"Problem while attempting to access data file URL '{data_file_path}':  {error}\"\n                )\n\n            if response.status_code != 200:\n                raise FileNotFoundError(f\"Bad response while accessing \"\n                                        f\"data file URL '{data_file_path}'.\"\n                                        )\n            try:\n                site_data = response.json()\n            except Exception as error:\n                raise ValueError(\n                    f\"Problem parsing json contents at '{data_file_path}':  {error}.\"\n                )\n\n        else:\n            # Reference is to a file.\n            try:\n                with open(data_file_path, \"r\", encoding=\"utf-8\") as file:\n                    try:\n                        site_data = json.load(file)\n                    except Exception as error:\n                        raise ValueError(\n                            f\"Problem parsing json contents at '{data_file_path}':  {error}.\"\n                        )\n\n            except FileNotFoundError:\n                raise FileNotFoundError(f\"Problem while attempting to access \"\n                                        f\"data file '{data_file_path}'.\"\n                                        )\n\n        site_data.pop('$schema', None)\n\n        if honor_exclusions:\n            try:\n                response = requests.get(url=EXCLUSIONS_URL, timeout=10)\n                if response.status_code == 200:\n                    exclusions = response.text.splitlines()\n                    exclusions = [exclusion.strip() for exclusion in exclusions]\n\n                    for site in do_not_exclude:\n                        if site in exclusions:\n                            exclusions.remove(site)\n\n                    for exclusion in exclusions:\n                        try:\n                            site_data.pop(exclusion, None)\n                        except KeyError:\n                            pass\n\n            except Exception:\n                # If there was any problem loading the exclusions, just continue without them\n                print(\"Warning: Could not load exclusions, continuing without them.\")\n                honor_exclusions = False\n\n        self.sites = {}\n\n        # Add all site information from the json file to internal site list.\n        for site_name in site_data:\n            try:\n\n                self.sites[site_name] = \\\n                    SiteInformation(site_name,\n                                    site_data[site_name][\"urlMain\"],\n                                    site_data[site_name][\"url\"],\n                                    site_data[site_name][\"username_claimed\"],\n                                    site_data[site_name],\n                                    site_data[site_name].get(\"isNSFW\",False)\n\n                                    )\n            except KeyError as error:\n                raise ValueError(\n                    f\"Problem parsing json contents at '{data_file_path}':  Missing attribute {error}.\"\n                )\n            except TypeError:\n                print(f\"Encountered TypeError parsing json contents for target '{site_name}' at {data_file_path}\\nSkipping target.\\n\")\n\n        return\n\n    def remove_nsfw_sites(self, do_not_remove: list = []):\n        \"\"\"\n        Remove NSFW sites from the sites, if isNSFW flag is true for site\n\n        Keyword Arguments:\n        self                   -- This object.\n\n        Return Value:\n        None\n        \"\"\"\n        sites = {}\n        do_not_remove = [site.casefold() for site in do_not_remove]\n        for site in self.sites:\n            if self.sites[site].is_nsfw and site.casefold() not in do_not_remove:\n                continue\n            sites[site] = self.sites[site]\n        self.sites =  sites\n\n    def site_name_list(self):\n        \"\"\"Get Site Name List.\n\n        Keyword Arguments:\n        self                   -- This object.\n\n        Return Value:\n        List of strings containing names of sites.\n        \"\"\"\n\n        return sorted([site.name for site in self], key=str.lower)\n\n    def __iter__(self):\n        \"\"\"Iterator For Object.\n\n        Keyword Arguments:\n        self                   -- This object.\n\n        Return Value:\n        Iterator for sites object.\n        \"\"\"\n\n        for site_name in self.sites:\n            yield self.sites[site_name]\n\n    def __len__(self):\n        \"\"\"Length For Object.\n\n        Keyword Arguments:\n        self                   -- This object.\n\n        Return Value:\n        Length of sites object.\n        \"\"\"\n        return len(self.sites)\n"
  },
  {
    "path": "the_big_brother/validators/headless_validator.py",
    "content": "from dataclasses import dataclass\nfrom typing import Optional\n\ntry:\n    from playwright.sync_api import sync_playwright\nexcept ImportError:\n    sync_playwright = None\n\n@dataclass\nclass LinkValidationResult:\n    url: str\n    is_profile: bool\n    reason: Optional[str] = None\n    title: Optional[str] = None\n    final_url: Optional[str] = None\n    visible_text: Optional[str] = None\n\nclass HeadlessValidator:\n    def __init__(self, headless: bool = True):\n        self.headless = headless\n        self.playwright = None\n        self.browser = None\n        if sync_playwright is None:\n            print(\"Warning: Playwright is not installed. Headless validation will fail.\")\n\n    def __enter__(self):\n        if sync_playwright:\n            self.playwright = sync_playwright().start()\n            self.browser = self.playwright.chromium.launch(headless=self.headless)\n        return self\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        if self.browser:\n            self.browser.close()\n        if self.playwright:\n            self.playwright.stop()\n\n    def validate(self, url: str) -> LinkValidationResult:\n        if not self.browser:\n            if not sync_playwright:\n                 return LinkValidationResult(url, False, reason=\"Playwright not installed\")\n            # If used without context manager, launch for single use (slower)\n            with sync_playwright() as p:\n                browser = p.chromium.launch(headless=self.headless)\n                return self._validate_with_browser(browser, url)\n        \n        return self._validate_with_browser(self.browser, url)\n\n    def _validate_with_browser(self, browser, url: str) -> LinkValidationResult:\n        context = browser.new_context(\n            user_agent=\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36\"\n        )\n        page = context.new_page()\n        try:\n            # Navigate\n            response = page.goto(url, timeout=30000, wait_until=\"domcontentloaded\")\n            \n            if not response:\n                page.close()\n                return LinkValidationResult(url, False, reason=\"No response\")\n\n            final_url = page.url\n            title = page.title()\n            \n            # Status check\n            if response.status >= 400:\n                page.close()\n                return LinkValidationResult(url, False, reason=f\"HTTP {response.status}\", title=title, final_url=final_url)\n\n            # Heuristic 1: Title content\n            error_keywords_title = [\"page not found\", \"404\", \"not found\", \"doesn't exist\", \"does not exist\", \"user not found\"]\n            if any(k in title.lower() for k in error_keywords_title):\n                page.close()\n                return LinkValidationResult(url, False, reason=\"Title indicated 404\", title=title, final_url=final_url)\n\n            # Get text\n            body_text = page.evaluate(\"document.body.innerText\")\n            validation_text = body_text[:1000] # First 1000 chars\n\n            # Heuristic 2: Body content\n            error_keywords_body = [\"this page isn't available\", \"sorry, this content isn't available right now\"]\n            if any(k in body_text.lower() for k in error_keywords_body):\n                 page.close()\n                 return LinkValidationResult(url, False, reason=\"Body content indicated 404\", title=title, final_url=final_url)\n            \n            page.close()\n            return LinkValidationResult(url, True, title=title, final_url=final_url, visible_text=validation_text)\n\n        except Exception as e:\n            page.close()\n            return LinkValidationResult(url, False, reason=f\"Browsing error: {str(e)}\")\n"
  }
]