Repository: chadi0x/TheBigBrother
Branch: main
Commit: 3e9569acead3
Files: 45
Total size: 460.4 KB
Directory structure:
gitextract_16rro3kj/
├── .dockerignore
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── VERSION
├── docker-compose.yml
├── pyproject.toml
├── requirements.txt
└── the_big_brother/
├── __init__.py
├── __main__.py
├── gui/
│ ├── __init__.py
│ ├── main.py
│ └── static/
│ ├── 3.css
│ └── index.html
├── image_grabber.py
├── modules/
│ ├── __init__.py
│ ├── ai_analyst.py
│ ├── breach_vault.py
│ ├── code_hunter.py
│ ├── crypto_analyzer.py
│ ├── dark_watch.py
│ ├── digital_footprint.py
│ ├── domain_oracle.py
│ ├── dork_studio.py
│ ├── exif_analyzer.py
│ ├── flight_radar.py
│ ├── geoint_spy.py
│ ├── mail_tracer.py
│ ├── network_mapper.py
│ ├── paste_dragnet.py
│ ├── phantom_id.py
│ ├── shadow_map.py
│ ├── sigint_sweep.py
│ ├── ssl_sentinel.py
│ └── wayback_spectre.py
├── notify.py
├── py.typed
├── resources/
│ ├── data.json
│ └── data.schema.json
├── result.py
├── reverse_search.py
├── scanner.py
├── sites.py
└── validators/
└── headless_validator.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env
venv
.venv
pip-log.txt
pip-delete-this-directory.txt
.tox
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.log
.git
.gitignore
.mypy_cache
.pytest_cache
.hydra
.DS_Store
================================================
FILE: .gitignore
================================================
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# with no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
.idea/
# VS Code
.vscode/
# MacOS
.DS_Store
================================================
FILE: Dockerfile
================================================
FROM mcr.microsoft.com/playwright/python:v1.49.0-jammy
WORKDIR /app
# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Force-install Chromium that matches the pip-installed Playwright version.
# This guarantees the chromium_headless_shell binary path matches whatever
# playwright pip resolves to, even if the base image's bundled browser is
# from a different release.
RUN playwright install chromium
# Copy application code
COPY . .
EXPOSE 8000
CMD ["python", "-m", "uvicorn", "the_big_brother.gui.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2025 The Big Brother
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# ◆ 👁️THE BIG BROTHER · V5.0 👁️◆
```text
╔══════════════════════════════════════════════════════════════════════════╗
║ ║
║ ████████╗██╗ ██╗███████╗ ██████╗ ██╗ ██████╗ ║
║ ╚══██╔══╝██║ ██║██╔════╝ ██╔══██╗██║██╔════╝ ║
║ ██║ ███████║█████╗ ██████╔╝██║██║ ███╗ ║
║ ██║ ██╔══██║██╔══╝ ██╔══██╗██║██║ ██║ ║
║ ██║ ██║ ██║███████╗ ██████╔╝██║╚██████╔╝ ║
║ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═════╝ ║
║ ║
║ ██████╗ ██████╗ ██████╗ ████████╗██╗ ██╗███████╗██████╗ ║
║ ██╔══██╗██╔══██╗██╔═══██╗╚══██╔══╝██║ ██║██╔════╝██╔══██╗ ║
║ ██████╔╝██████╔╝██║ ██║ ██║ ███████║█████╗ ██████╔╝ ║
║ ██╔══██╗██╔══██╗██║ ██║ ██║ ██╔══██║██╔══╝ ██╔══██╗ ║
║ ██████╔╝██║ ██║╚██████╔╝ ██║ ██║ ██║███████╗██║ ██║ ║
║ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ║
║ ║
║ ◇ THE EYE THAT NEVER BLINKS — V5.0 ◇ ║
║ ULTRA-DEEP OSINT · 21 INTEL MODULES ║
║ ║
╚══════════════════════════════════════════════════════════════════════════╝
```
**Advanced OSINT Framework · Premium Holographic Dashboard · 21 Intel Modules**
[](https://github.com/chadi0x/the-big-brother/releases)
[](LICENSE)
[](https://www.python.org/)
[](https://www.docker.com/)
[](https://github.com/chadi0x/)
[`★ WHAT'S NEW`](#-whats-new-in-v50) · [`★ MODULES`](#-the-21-modules) · [`★ INSTALL`](#-installation) · [`★ PREMIUM`](#-premium-projects) · [`★ TELEGRAM`](https://t.me/hisoka0morow)
---
## ⟦ OVERVIEW ⟧
**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.
```
┌─────────────────────────────────────────────────────────────┐
│ 21 MODULES · 1 UNIFIED ENGINE · FULL DOCKER STACK │
│ HOLOGRAPHIC HUD · PARTICLE GRID · ANIMATED INTEL TIMELINE │
└─────────────────────────────────────────────────────────────┘
```
---
## 🆕 WHAT'S NEW IN V5.0
### ◆ Six brand-new intel modules
| Tool | Capability |
|:--|:--|
| 🤖 **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 |
| 🌐 **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 |
| 📨 **MAIL TRACER** | MX validity · SPF/DMARC posture · disposable/role/free-provider detection · Gravatar identity bind · 0-100 deliverability trust score with factor breakdown |
| 🐙 **CODE HUNTER** | GitHub OSINT via public API · top repos · language stats · **public commit-email harvesting** · 24h activity heatmap · org enumeration · influence score |
| 🕰️ **WAYBACK SPECTRE** | Wayback Machine CDX timeline · yearly distribution bars · auto-flags sensitive historical paths (admin · `.env` · `.git` · backups · `.bak` · `id_rsa` · 25+ patterns) |
| 📋 **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 |
---
## ⚡ THE 21 MODULES
```text
┌─────────────────────────────────────────────────────────────────┐
│ ⟦ SYNTHESIS ⟧ │
├─────────────────────────────────────────────────────────────────┤
│ 🤖 AI ANALYST ── cross-module synthesis · NEW │
├─────────────────────────────────────────────────────────────────┤
│ ⟦ IDENTITY ⟧ │
├─────────────────────────────────────────────────────────────────┤
│ 🔍 TARGET PROFILER ── 200+ social enum + biometric capture │
│ 👻 PHANTOM ID ── cross-platform identity correlation │
│ 🔓 BREACH VAULT ── HaveIBeenPwned + paste-dump aggregator │
│ 👣 DIGITAL FOOTPRINT ─ phone + email enumeration │
│ 📨 MAIL TRACER ── email forensics · NEW │
│ 🐙 CODE HUNTER ── GitHub OSINT · NEW │
├─────────────────────────────────────────────────────────────────┤
│ ⟦ INTEL ⟧ │
├─────────────────────────────────────────────────────────────────┤
│ 📡 SIGINT SWEEP ── Reddit · HN · News · X aggregator │
│ 🗺️ SHADOW MAP ── AbuseIPDB · VT · URLhaus reputation │
│ 📋 PASTE DRAGNET ── 12-site paste hunter · NEW │
│ 🧅 DARK WEB ── Ahmia + ransomware leak feeds │
│ 🕰️ WAYBACK SPECTRE ── archive timeline + leak flags · NEW │
├─────────────────────────────────────────────────────────────────┤
│ ⟦ INFRASTRUCTURE ⟧ │
├─────────────────────────────────────────────────────────────────┤
│ 🌐 DOMAIN ORACLE ── deep domain intel + posture · NEW │
│ 🕸️ NETWORK MAPPER ── async ports + pyvis network graph │
│ 🔒 SSL SENTINEL ── certificate chain + SAN extraction │
│ 🪙 CRYPTO ── BTC/ETH blockchain wallet recon │
├─────────────────────────────────────────────────────────────────┤
│ ⟦ MEDIA · GEO ⟧ │
├─────────────────────────────────────────────────────────────────┤
│ 📸 EXIF X-RAY ── metadata + GPS extraction │
│ 🛠️ DORK STUDIO ── Google/Shodan/GitHub dork generator │
│ 🛰️ GEOINT SPY ── multi-engine coordinate intel │
│ ✈️ SKY RADAR ── live OpenSky aircraft tracking │
└─────────────────────────────────────────────────────────────────┘
```
---
## 🚀 INSTALLATION
### ⟦ PROTOCOL A · Docker (Recommended) ⟧
```bash
# 1. Clone the grid
git clone https://github.com/chadi0x/TheBigBrother.git
cd TheBigBrother
# 2. Boot the eye
docker-compose up --build -d
```
> ▸ **Access:** `http://localhost:8000`
> ▸ **Stop:** `docker-compose down`
> ▸ **Logs:** `docker-compose logs -f`
### ⟦ PROTOCOL B · Manual Bare-Metal ⟧
**Prerequisites:** Python `3.10+` · Playwright
```bash
# 1. Environment
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# 2. Dependencies
pip install -r requirements.txt
playwright install chromium
# 3. Launch
uvicorn the_big_brother.gui.main:app --host 0.0.0.0 --port 8000
```
---
## 📸 SCREENSHOTS
---
## ◆ THE STACK
| Layer | Tech |
|:--|:--|
| **Backend** | FastAPI · Python 3.10+ · async-first orchestration |
| **Frontend** | Vanilla JS · Leaflet · custom particle engine |
| **Recon** | Playwright (headless validation) · pyvis · dnspython · python-whois · DuckDuckGo |
| **Container** | Docker + docker-compose · single-command deploy |
```
┌──────────────┐
│ BROWSER │
│ (HUD · JS) │
└──────┬───────┘
│ /api/*
▼
┌──────────────┐
│ FASTAPI │ ← 22 endpoints
└──────┬───────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ SCANNER │ │ MODULES │ │HEADLESS │
│ (200+ │ │ (21) │ │PLAYWRGHT│
│ sites) │ │ │ │ valid. │
└─────────┘ └─────────┘ └─────────┘
│ │ │
└────────────────▼────────────────┘
PUBLIC OSINT ENDPOINTS
(rdap · crt.sh · wayback · github · etc)
```
---
## 🔥 PREMIUM PROJECTS
For operators who need the full arsenal — exclusive offerings via Telegram.
- 👁️ **TheBigBrother God-EYE** · Proprietary servers · extended API access · zero-key deep intel
- 🐦 **X GodMode** · Twitter/X automation suite — engagement & sentiment engineering
- 🎵 **Amplifior** · Autonomous TikTok empire builder
- 🎣 **Psychopath** · Engineered campaign deployment with AI conversational bots
- 🛠️ **Custom Builds** · 200+ ready-to-deploy operational frameworks. If it doesn't exist, we build it.
**[▸ INQUIRE · Telegram @hisoka0morow](https://t.me/hisoka0morow)**
---
## ⚠️ DISCLAIMER
This 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.
> **CLASSIFIED · AUTHORIZED PERSONNEL ONLY**
---
## 📞 SUPPORT · COMMUNITY
- **Creator / Architect** — `CHADI0X`
- **Telegram (direct inquiries)** — [@hisoka0morow](https://t.me/hisoka0morow)
- **GitHub** — [@chadi0x](https://github.com/chadi0x)
---
## 📜 LICENSE
MIT License — see [LICENSE](LICENSE)
---
```
◆ ─────────────────────────────────────────────────── ◆
"In the digital age, anonymity is a luxury.
Information is the currency of power."
── CHADI0X
◆ ─────────────────────────────────────────────────── ◆
```
**V5.0.0 · THE EYE THAT NEVER BLINKS · ◇**
*Built by CHADI0X · Quantum OSINT Surveillance Grid*
================================================
FILE: VERSION
================================================
2.1.0
================================================
FILE: docker-compose.yml
================================================
services:
the-big-brother:
build: .
ports:
- "8000:8000"
volumes:
- .:/app
environment:
- PYTHONUNBUFFERED=1
restart: unless-stopped
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "the-big-brother"
version = "2.1.0"
description = "Advanced OSINT Tool for Username Intelligence & Visual Reconnaissance"
readme = "README.md"
requires-python = ">=3.9"
license = {text = "MIT"}
authors = [
{name = "CHADI0X", email = "chadi0x@example.com"}
]
keywords = ["osint", "reconnaissance", "username", "reverse-image-search", "intelligence"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Information Technology",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Security",
]
dependencies = [
"fastapi>=0.104.0",
"uvicorn>=0.24.0",
"requests>=2.31.0",
"beautifulsoup4>=4.12.0",
"playwright>=1.40.0",
"duckduckgo-search>=3.9.0",
"tomli>=2.0.0; python_version<'3.11'",
"pandas>=2.0.0",
"requests-futures>=1.0.0",
"colorama>=0.4.6",
"openpyxl>=3.1.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"black>=23.0.0",
"mypy>=1.0.0",
]
[project.urls]
Homepage = "https://github.com/chadi0x/the-big-brother"
Repository = "https://github.com/chadi0x/the-big-brother"
Issues = "https://github.com/chadi0x/the-big-brother/issues"
[project.scripts]
the-big-brother = "the_big_brother.__main__:main"
[tool.setuptools]
packages = ["the_big_brother"]
[tool.setuptools.package-data]
the_big_brother = ["py.typed", "gui/**/*", "resources/**/*"]
================================================
FILE: requirements.txt
================================================
fastapi>=0.104.0
uvicorn>=0.24.0
requests>=2.31.0
beautifulsoup4>=4.12.0
playwright==1.49.0
duckduckgo-search>=3.9.0
tomli>=2.0.0
pandas>=2.0.0
requests-futures>=1.0.0
colorama>=0.4.6
openpyxl>=3.1.0
holehe>=1.60
phonenumbers>=8.12.0
dnspython>=2.4.0
pyvis>=0.3.0
networkx>=3.0.0
Pillow>=9.0.0
python-whois>=0.9.0
python-multipart>=0.0.6
aiohttp>=3.9.0
================================================
FILE: the_big_brother/__init__.py
================================================
""" The Big Brother Module
This module contains the main logic to search for usernames at social
networks.
"""
from importlib.metadata import version as pkg_version, PackageNotFoundError
import pathlib
import tomli
def get_version() -> str:
"""Fetch the version number of the installed package."""
try:
return pkg_version("the_big_brother")
except PackageNotFoundError:
# Try reading from pyproject.toml (setuptools format)
pyproject_path: pathlib.Path = pathlib.Path(__file__).resolve().parent.parent / "pyproject.toml"
if pyproject_path.exists():
with pyproject_path.open("rb") as f:
pyproject_data = tomli.load(f)
# Use setuptools format: project.version instead of tool.poetry.version
if "project" in pyproject_data and "version" in pyproject_data["project"]:
return pyproject_data["project"]["version"]
# Fallback to VERSION file
version_path: pathlib.Path = pathlib.Path(__file__).resolve().parent.parent / "VERSION"
if version_path.exists():
return version_path.read_text().strip()
return "2.1.0" # Final fallback
# This variable is only used to check for ImportErrors induced by users running as script rather than as module or package
import_error_test_var = None
__shortname__ = "The Big Brother"
__longname__ = "The Big Brother: Find Usernames Across Social Networks"
__version__ = get_version()
# Update check disabled for now or point to new repo if exists
forge_api_latest_release = ""
================================================
FILE: the_big_brother/__main__.py
================================================
#! /usr/bin/env python3
"""
Sherlock: Find Usernames Across Social Networks Module
This module contains the main logic to search for usernames at social
networks.
"""
import sys
if __name__ == "__main__":
# Check if the user is using the correct version of Python
python_version = sys.version.split()[0]
if sys.version_info < (3, 9):
print(f"The Big Brother requires Python 3.9+\nYou are using Python {python_version}, which is not supported by The Big Brother.")
sys.exit(1)
from the_big_brother import scanner
scanner.main()
================================================
FILE: the_big_brother/gui/__init__.py
================================================
================================================
FILE: the_big_brother/gui/main.py
================================================
from fastapi import FastAPI, BackgroundTasks, Response, UploadFile, File, Form
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from uuid import uuid4
import os
import sys
import io
import csv
from typing import List, Optional
# Add parent directory to path to allow imports
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
from the_big_brother.scanner import scan, SitesInformation, QueryNotify, QueryStatus
from the_big_brother.image_grabber import fetch_images, fetch_images_with_diag
from the_big_brother.reverse_search import ReverseImageSearcher
from the_big_brother.validators.headless_validator import HeadlessValidator
from the_big_brother.modules.digital_footprint import get_phone_info, run_holehe
from the_big_brother.modules.network_mapper import scan_target, generate_network_map
from the_big_brother.modules.dark_watch import search_dark_web
from the_big_brother.modules.crypto_analyzer import analyze_crypto
from the_big_brother.modules.ssl_sentinel import get_ssl_info
from the_big_brother.modules.exif_analyzer import get_exif_data
from the_big_brother.modules.dork_studio import generate_dorks
from the_big_brother.modules.geoint_spy import get_geoint_data
from the_big_brother.modules.flight_radar import get_flight_radar
# New V4 modules
from the_big_brother.modules.phantom_id import phantom_id_search
from the_big_brother.modules.breach_vault import breach_vault_search
from the_big_brother.modules.sigint_sweep import sigint_sweep
from the_big_brother.modules.shadow_map import shadow_map_analyze
# New V5 modules
from the_big_brother.modules.domain_oracle import domain_oracle
from the_big_brother.modules.mail_tracer import mail_tracer
from the_big_brother.modules.code_hunter import code_hunter
from the_big_brother.modules.wayback_spectre import wayback_spectre
from the_big_brother.modules.paste_dragnet import paste_dragnet
from the_big_brother.modules.ai_analyst import ai_analyst
class FootprintRequest(BaseModel):
query: str
type: str # "email" or "phone"
class NetworkRequest(BaseModel):
domain: str
class DarkRequest(BaseModel):
query: str
class CryptoRequest(BaseModel):
address: str
coin: str
class SSLRequest(BaseModel):
domain: str
class ExifRequest(BaseModel):
url: str
class DorkRequest(BaseModel):
target: str
domain: str = ""
class DeepSearchRequest(BaseModel):
image_url: str
class GeointRequest(BaseModel):
lat: str
lon: str
class FlightRequest(BaseModel):
lat: float
lon: float
radius: float = 100
class PhantomRequest(BaseModel):
username: str
class BreachRequest(BaseModel):
query: str
type: str = "email"
class SigintRequest(BaseModel):
query: str
class ShadowMapRequest(BaseModel):
target: str
class DomainOracleRequest(BaseModel):
domain: str
class MailTracerRequest(BaseModel):
email: str
class CodeHunterRequest(BaseModel):
username: str
class WaybackRequest(BaseModel):
target: str
class PasteRequest(BaseModel):
query: str
class AIAnalystRequest(BaseModel):
target: str
mode: str = "auto"
app = FastAPI(title="The Big Brother V5 API")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# In-memory storage
class JobState:
def __init__(self):
self.status = "running"
self.results = []
self.images = []
self.image_diag = ""
self.stop_requested = False
jobs: dict[str, JobState] = {}
class ScanRequest(BaseModel):
username: str
class NotifyQueue(QueryNotify):
def __init__(self, job_id, jobs_dict):
self.job_id = job_id
self.jobs = jobs_dict
super().__init__()
def update(self, result):
if self.jobs[self.job_id].stop_requested:
raise InterruptedError("Stopped by user")
if result.status == QueryStatus.CLAIMED:
self.jobs[self.job_id].results.append({
"site": result.site_name,
"url": result.site_url_user,
"status": "Found",
"validation": "Pending",
"context": result.context
})
elif result.status == QueryStatus.WAF:
self.jobs[self.job_id].results.append({
"site": result.site_name,
"url": result.site_url_user,
"status": "WAF Blocked",
"validation": "Pending",
"context": result.context
})
def start(self, message=None):
pass
def finish(self, message=None):
pass
def run_scan_job(job_id: str, username: str):
try:
# Handle spaces: Check "John Doe" and "JohnDoe" (or replace space with nothing)
usernames_to_check = [username]
if " " in username:
usernames_to_check.append(username.replace(" ", ""))
# 1. Fetch Images (only for the primary username) — capture diagnostics
try:
images, diag = fetch_images_with_diag(username, limit=6)
jobs[job_id].images = images
jobs[job_id].image_diag = diag
except Exception as e:
print(f"Image fetch error: {e}")
jobs[job_id].image_diag = f"fatal: {e}"
# 2. Run Scan
# Use local data.json file to ensure all sites are loaded
# Get the path to the local data.json file
data_file_path = os.path.join(os.path.dirname(__file__), "..", "resources", "data.json")
sites_info = SitesInformation(data_file_path=data_file_path, honor_exclusions=False)
site_data = {site.name: site.information for site in sites_info}
notify = NotifyQueue(job_id, jobs)
try:
for u in usernames_to_check:
if jobs[job_id].stop_requested: break
scan(u, site_data, notify)
except InterruptedError:
jobs[job_id].status = "stopped"
return
if jobs[job_id].stop_requested:
jobs[job_id].status = "stopped"
return
# 3. Validate
jobs[job_id].status = "validating"
validate_results(job_id)
if jobs[job_id].stop_requested:
jobs[job_id].status = "stopped"
else:
jobs[job_id].status = "completed"
except Exception as e:
import traceback
traceback.print_exc()
print(f"Error in scan job: {e}")
jobs[job_id].status = "error"
def validate_results(job_id: str):
results = jobs[job_id].results
if not results:
return
to_validate = [r for r in results if r["status"] == "Found"]
if not to_validate:
return
try:
with HeadlessValidator(headless=True) as validator:
for res in to_validate:
if jobs[job_id].stop_requested:
break
res["validation"] = "Checking..."
val_res = validator.validate(res["url"])
if val_res.is_profile:
res["validation"] = "Verified"
res["page_title"] = val_res.title
res["snippet"] = val_res.visible_text[:200] if val_res.visible_text else ""
else:
res["validation"] = "False Positive"
res["reason"] = val_res.reason
except Exception as e:
print(f"Validation error: {e}")
@app.post("/api/scan")
async def start_scan(request: ScanRequest, background_tasks: BackgroundTasks):
job_id = str(uuid4())
jobs[job_id] = JobState()
background_tasks.add_task(run_scan_job, job_id, request.username)
return {"job_id": job_id}
@app.post("/api/stop/{job_id}")
async def stop_scan(job_id: str):
if job_id in jobs:
jobs[job_id].stop_requested = True
return {"status": "stopping"}
return {"error": "Job not found"}
@app.get("/api/results/{job_id}")
async def get_results(job_id: str):
if job_id not in jobs:
return {"error": "Job not found"}
return {
"status": jobs[job_id].status,
"results": jobs[job_id].results,
"images": jobs[job_id].images,
"image_diag": jobs[job_id].image_diag,
}
@app.get("/api/download/{job_id}")
async def download_report(job_id: str):
if job_id not in jobs:
return {"error": "Job not found"}
results = jobs[job_id].results
output = io.StringIO()
writer = csv.writer(output)
writer.writerow(["Site", "URL", "Status", "Validation", "Page Title"])
for r in results:
writer.writerow([
r.get("site"),
r.get("url"),
r.get("status"),
r.get("validation"),
r.get("page_title", "")
])
return Response(
content=output.getvalue(),
media_type="text/csv",
headers={"Content-Disposition": f"attachment; filename=report_{job_id}.csv"}
)
@app.post("/api/deep-search")
async def deep_search(request: DeepSearchRequest):
searcher = ReverseImageSearcher(headless=True)
results = await searcher.search(request.image_url)
return results
@app.post("/api/footprint")
async def footprint_scan(request: FootprintRequest):
if request.type == "phone":
return get_phone_info(request.query)
elif request.type == "email":
return await run_holehe(request.query)
elif request.type == "breach":
# Redirect to new breach vault
return await breach_vault_search(request.query, "email")
return {"error": "Invalid type"}
@app.post("/api/phantom")
async def phantom_scan(request: PhantomRequest):
return await phantom_id_search(request.username)
@app.post("/api/breach")
async def breach_scan(request: BreachRequest):
return await breach_vault_search(request.query, request.type)
@app.post("/api/sigint")
async def sigint_scan(request: SigintRequest):
return await sigint_sweep(request.query)
@app.post("/api/shadowmap")
async def shadowmap_scan(request: ShadowMapRequest):
return await shadow_map_analyze(request.target)
@app.post("/api/network/scan")
async def network_scan(request: NetworkRequest):
data = await scan_target(request.domain)
# Generate map HTML
if "error" not in data:
graph_html = generate_network_map(data)
data["map_html"] = graph_html
return data
@app.post("/api/dark/search")
async def dark_search(request: DarkRequest):
return await search_dark_web(request.query)
@app.post("/api/crypto/analyze")
async def crypto_analyze(request: CryptoRequest):
return analyze_crypto(request.address, request.coin)
@app.post("/api/ssl/scan")
async def ssl_scan(request: SSLRequest):
return get_ssl_info(request.domain)
@app.post("/api/tools/exif")
async def tool_exif(request: ExifRequest):
return get_exif_data(request.url)
# FILE UPLOAD for EXIF
@app.post("/api/tools/exif/upload")
async def tool_exif_upload(file: UploadFile = File(...)):
# Read bytes
content = await file.read()
# Modify get_exif_data to accept bytes.
# Since we can't easily modify the module function signature without breaking it elsewhere or refactoring,
# let's duplicate the logic here or update the module.
# Actually, let's update the module logic in-place via a helper if possible.
# But for now, let's pass a byte stream if the module supports it or just use PIL directly here.
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS
from io import BytesIO
results = {"source": file.filename, "basic": {}, "gps": {}, "error": None}
try:
image = Image.open(BytesIO(content))
results["basic"]["format"] = image.format
results["basic"]["mode"] = image.mode
results["basic"]["size"] = f"{image.width}x{image.height}"
exif_data = image._getexif()
if exif_data:
for tag_id, value in exif_data.items():
tag = TAGS.get(tag_id, tag_id)
if isinstance(value, bytes):
try: value = value.decode()
except: value = str(value)
if tag == "GPSInfo":
gps_data = {}
for t in value:
sub_tag = GPSTAGS.get(t, t)
gps_data[sub_tag] = str(value[t])
results["gps"] = gps_data
else:
if len(str(value)) < 500:
results["basic"][tag] = value
except Exception as e:
results["error"] = str(e)
return results
@app.post("/api/tools/dork")
async def tool_dork(request: DorkRequest):
return generate_dorks(request.target, request.domain)
@app.post("/api/tools/geoint")
async def tool_geoint(request: GeointRequest):
return get_geoint_data(request.lat, request.lon)
@app.post("/api/tools/flight")
async def tool_flight(request: FlightRequest):
return get_flight_radar(request.lat, request.lon, request.radius)
# === V5 endpoints ===
@app.post("/api/oracle")
async def oracle_scan(request: DomainOracleRequest):
return await domain_oracle(request.domain)
@app.post("/api/mailtracer")
async def mail_scan(request: MailTracerRequest):
return await mail_tracer(request.email)
@app.post("/api/codehunter")
async def code_scan(request: CodeHunterRequest):
return await code_hunter(request.username)
@app.post("/api/wayback")
async def wayback_scan(request: WaybackRequest):
return await wayback_spectre(request.target)
@app.post("/api/paste")
async def paste_scan(request: PasteRequest):
return await paste_dragnet(request.query)
@app.post("/api/analyst")
async def analyst_scan(request: AIAnalystRequest):
return await ai_analyst(request.target, request.mode)
# Serve static files for frontend
static_dir = os.path.join(os.path.dirname(__file__), "static")
if os.path.exists(static_dir):
app.mount("/", StaticFiles(directory=static_dir, html=True), name="static")
================================================
FILE: the_big_brother/gui/static/3.css
================================================
/* ============================================================
THE BIG BROTHER V5 — EVOLVED CYBERPUNK
Premium HUD theme · holographic glass · max-FX motion layer
============================================================ */
@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');
/* ---------- design tokens ---------- */
:root {
--bg-0: #030308;
--bg-1: #07070f;
--bg-2: #0c0c18;
--bg-3: #131325;
--panel: rgba(10, 10, 22, 0.72);
--panel-2: rgba(14, 14, 30, 0.85);
--panel-edge: rgba(255, 0, 80, 0.28);
--panel-edge-cyan: rgba(0, 240, 255, 0.28);
--text: #e6e9f5;
--text-mute: #8a8fa8;
--text-dim: #5a5e75;
--red: #ff0050;
--red-bright: #ff3a73;
--red-deep: #b8002f;
--cyan: #00f0ff;
--cyan-bright: #6cfcff;
--magenta: #ff00d4;
--green: #00ff8c;
--amber: #ffb800;
--violet: #a259ff;
--grad-hot: linear-gradient(135deg, #ff0050 0%, #ff00d4 100%);
--grad-cool: linear-gradient(135deg, #00f0ff 0%, #a259ff 100%);
--grad-warn: linear-gradient(135deg, #ffb800 0%, #ff0050 100%);
--grad-ok: linear-gradient(135deg, #00ff8c 0%, #00f0ff 100%);
--grad-holo: linear-gradient(135deg, #ff0050, #a259ff 25%, #00f0ff 55%, #00ff8c 80%, #ffb800);
--grad-panel: linear-gradient(135deg, rgba(255,0,80,0.06), rgba(0,240,255,0.06));
--shadow-neon: 0 0 24px rgba(255, 0, 80, 0.35), 0 0 60px rgba(255, 0, 80, 0.12);
--shadow-cyan: 0 0 24px rgba(0, 240, 255, 0.35), 0 0 60px rgba(0, 240, 255, 0.12);
--shadow-panel: 0 14px 40px -16px rgba(0,0,0,0.7), inset 0 1px 0 rgba(255,255,255,0.04);
--radius: 14px;
--radius-sm: 8px;
--radius-pill: 999px;
--transition: 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
--transition-slow: 480ms cubic-bezier(0.2, 0.8, 0.2, 1);
--font-display: 'Orbitron', sans-serif;
--font-mono: 'JetBrains Mono', 'Share Tech Mono', monospace;
--font-ui: 'Inter', system-ui, sans-serif;
}
* { box-sizing: border-box; }
*::selection { background: var(--red); color: #000; }
html, body {
margin: 0; padding: 0; height: 100%;
background: var(--bg-0);
color: var(--text);
font-family: var(--font-ui);
overflow: hidden;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
body::before, body::after {
content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 0;
}
body::before {
background:
radial-gradient(60% 50% at 10% 10%, rgba(255, 0, 80, 0.18), transparent 60%),
radial-gradient(50% 40% at 90% 20%, rgba(0, 240, 255, 0.15), transparent 60%),
radial-gradient(40% 40% at 50% 95%, rgba(162, 89, 255, 0.16), transparent 60%);
animation: auroraDrift 22s ease-in-out infinite alternate;
filter: blur(20px);
}
body::after {
background:
linear-gradient(rgba(255, 0, 80, 0.05) 1px, transparent 1px) 0 0 / 48px 48px,
linear-gradient(90deg, rgba(0, 240, 255, 0.05) 1px, transparent 1px) 0 0 / 48px 48px;
mask-image: radial-gradient(ellipse at center, black 30%, transparent 75%);
animation: gridPan 40s linear infinite;
}
@keyframes auroraDrift {
0% { transform: translate(0, 0) scale(1); }
100% { transform: translate(-3%, 2%) scale(1.06); }
}
@keyframes gridPan {
from { background-position: 0 0, 0 0; }
to { background-position: 480px 480px, 480px 480px; }
}
.scanlines {
position: fixed; inset: 0; pointer-events: none; z-index: 1;
background: repeating-linear-gradient(to bottom, rgba(255,255,255,0.012) 0 1px, transparent 1px 3px);
mix-blend-mode: overlay;
}
.vignette {
position: fixed; inset: 0; pointer-events: none; z-index: 2;
box-shadow: inset 0 0 220px rgba(0, 0, 0, 0.85);
}
#particle-canvas {
position: fixed; inset: 0; pointer-events: none; z-index: 1; opacity: 0.55;
}
.app-container {
position: relative; z-index: 5;
display: grid;
grid-template-columns: 280px 1fr;
width: 100%; height: 100vh;
}
.sidebar {
background: linear-gradient(180deg, rgba(8,8,18,0.92), rgba(8,8,18,0.78));
backdrop-filter: blur(18px) saturate(140%);
-webkit-backdrop-filter: blur(18px) saturate(140%);
border-right: 1px solid var(--panel-edge);
display: flex; flex-direction: column; overflow: hidden;
position: relative;
}
.sidebar::before {
content: ''; position: absolute; top: 0; right: -1px;
width: 1px; height: 100%;
background: linear-gradient(180deg, transparent, var(--red), var(--cyan), transparent);
opacity: 0.7; animation: edgePulse 6s ease-in-out infinite;
}
@keyframes edgePulse {
0%, 100% { opacity: 0.35; } 50% { opacity: 0.95; }
}
.sidebar-header {
padding: 26px 20px 22px;
border-bottom: 1px solid var(--panel-edge);
text-align: center; position: relative;
}
.brand-mark {
width: 64px; height: 64px; margin: 0 auto 12px;
border-radius: 50%;
background: var(--grad-holo);
background-size: 300% 300%;
animation: holoShift 8s linear infinite;
position: relative; display: grid; place-items: center;
box-shadow: 0 0 24px rgba(255, 0, 80, 0.45), inset 0 0 24px rgba(0, 240, 255, 0.25);
}
.brand-mark::after {
content: ''; position: absolute; inset: 4px;
border-radius: 50%;
background: radial-gradient(circle at 50% 35%, #1a1a2e, #050510);
}
.brand-mark span {
position: relative; font-size: 28px; z-index: 2;
filter: drop-shadow(0 0 8px var(--red));
}
.brand-mark svg {
position: relative;
z-index: 2;
width: 40px;
height: 40px;
filter: drop-shadow(0 0 10px rgba(255, 0, 80, 0.7))
drop-shadow(0 0 18px rgba(0, 240, 255, 0.35));
animation: markFloat 6s ease-in-out infinite;
}
@keyframes markFloat {
0%, 100% { transform: rotate(0deg) scale(1); }
50% { transform: rotate(2deg) scale(1.04); }
}
@keyframes holoShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.sidebar-header h1 {
margin: 0;
font-family: var(--font-display);
font-weight: 900; font-size: 1.35rem; letter-spacing: 4px;
background: var(--grad-hot);
-webkit-background-clip: text; background-clip: text;
color: transparent; position: relative;
text-shadow: 0 0 18px rgba(255, 0, 80, 0.3);
}
.sidebar-header h1.glitch::before,
.sidebar-header h1.glitch::after {
content: attr(data-text);
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: var(--grad-hot);
-webkit-background-clip: text; background-clip: text;
color: transparent; overflow: hidden;
}
.sidebar-header h1.glitch::before { animation: glitchTop 2.4s infinite linear alternate-reverse; }
.sidebar-header h1.glitch::after { animation: glitchBot 3.2s infinite linear alternate-reverse; }
@keyframes glitchTop {
0% { transform: translate(0, 0); clip-path: inset(0 0 80% 0); }
20% { transform: translate(-2px, 1px); clip-path: inset(0 0 70% 0); }
40% { transform: translate(1px, -1px); clip-path: inset(10% 0 60% 0); }
60% { transform: translate(-1px, 1px); clip-path: inset(20% 0 50% 0); }
100% { transform: translate(0, 0); clip-path: inset(0 0 80% 0); }
}
@keyframes glitchBot {
0% { transform: translate(0, 0); clip-path: inset(80% 0 0 0); }
25% { transform: translate(2px, -1px); clip-path: inset(70% 0 0 0); }
50% { transform: translate(-1px, 1px); clip-path: inset(60% 0 10% 0); }
75% { transform: translate(1px, -1px); clip-path: inset(50% 0 20% 0); }
100% { transform: translate(0, 0); clip-path: inset(80% 0 0 0); }
}
.sidebar-header .subtitle {
font-family: var(--font-mono); font-size: 0.65rem;
color: var(--text-mute); margin-top: 10px;
letter-spacing: 3px; text-transform: uppercase;
}
.status-strip {
margin-top: 14px;
display: flex; gap: 8px; justify-content: center;
font-family: var(--font-mono); font-size: 0.55rem;
color: var(--text-mute); letter-spacing: 1.5px;
}
.status-dot { display: inline-flex; align-items: center; gap: 5px; }
.status-dot::before {
content: ''; width: 6px; height: 6px; border-radius: 50%;
background: var(--green); box-shadow: 0 0 10px var(--green);
animation: pulse 1.5s ease-in-out infinite;
}
.status-dot.warn::before { background: var(--amber); box-shadow: 0 0 10px var(--amber); }
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.4; transform: scale(0.85); }
}
.nav-section-title {
padding: 18px 16px 8px;
font-family: var(--font-mono); font-size: 0.6rem;
color: var(--text-dim);
letter-spacing: 2px; text-transform: uppercase;
display: flex; align-items: center; gap: 8px;
}
.nav-section-title::after {
content: ''; flex: 1; height: 1px;
background: linear-gradient(90deg, var(--panel-edge), transparent);
}
.sidebar-nav {
flex: 1; overflow-y: auto;
padding: 6px 10px 16px;
display: flex; flex-direction: column; gap: 3px;
scrollbar-width: thin; scrollbar-color: var(--red) transparent;
}
.sidebar-nav::-webkit-scrollbar { width: 4px; }
.sidebar-nav::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, var(--red), var(--cyan));
border-radius: 4px;
}
.tab-btn {
display: flex; align-items: center; gap: 12px;
padding: 11px 14px;
background: transparent;
border: 1px solid transparent; border-left: 2px solid transparent;
border-radius: var(--radius-sm);
color: var(--text-mute);
font-family: var(--font-ui);
font-size: 0.78rem; font-weight: 500; letter-spacing: 1.2px;
text-align: left; cursor: pointer;
transition: var(--transition);
position: relative; overflow: hidden;
}
.tab-btn > span:first-child {
font-size: 1.05rem; width: 22px; text-align: center;
filter: grayscale(0.4); transition: var(--transition);
}
.tab-btn:hover {
color: var(--text);
border-color: var(--panel-edge);
background: rgba(255, 0, 80, 0.05);
transform: translateX(2px);
}
.tab-btn:hover > span:first-child { filter: grayscale(0) drop-shadow(0 0 4px var(--red)); }
.tab-btn.active {
color: #fff; border-left-color: var(--red);
background: linear-gradient(90deg, rgba(255, 0, 80, 0.18), rgba(255, 0, 80, 0.02) 80%);
box-shadow: inset 0 0 18px rgba(255, 0, 80, 0.08);
}
.tab-btn.active > span:first-child { filter: grayscale(0) drop-shadow(0 0 6px var(--red)); }
.tab-btn.active::after {
content: ''; position: absolute; right: 10px; top: 50%;
width: 4px; height: 4px;
background: var(--red); border-radius: 50%;
box-shadow: 0 0 8px var(--red);
transform: translateY(-50%); animation: pulse 1.4s ease-in-out infinite;
}
.tab-btn .new-badge {
margin-left: auto;
padding: 2px 6px;
font-family: var(--font-mono); font-size: 0.55rem; font-weight: 700; letter-spacing: 1px;
background: var(--grad-cool); color: #000;
border-radius: var(--radius-pill);
box-shadow: 0 0 12px rgba(0, 240, 255, 0.5);
}
.nav-premium-btn {
margin: 12px 10px;
padding: 13px 16px;
background: var(--grad-hot); background-size: 200% 200%;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: var(--radius-sm);
color: #fff;
font-family: var(--font-display); font-weight: 700; font-size: 0.78rem; letter-spacing: 2px;
cursor: pointer;
box-shadow: 0 0 24px rgba(255, 0, 80, 0.45);
animation: holoShift 5s linear infinite;
transition: var(--transition);
text-align: center;
display: flex; align-items: center; justify-content: center; gap: 8px;
}
.nav-premium-btn:hover {
transform: translateY(-2px) scale(1.02);
box-shadow: 0 0 32px rgba(255, 0, 80, 0.7);
}
.main-content {
display: flex; flex-direction: column;
overflow: hidden; min-width: 0;
}
.topbar {
height: 72px;
background: var(--panel);
backdrop-filter: blur(16px) saturate(140%);
-webkit-backdrop-filter: blur(16px) saturate(140%);
border-bottom: 1px solid var(--panel-edge);
display: flex; align-items: center; padding: 0 28px;
justify-content: space-between;
position: relative;
}
.topbar::after {
content: ''; position: absolute; bottom: -1px; left: 0;
height: 1px; width: 100%;
background: linear-gradient(90deg, transparent, var(--red), var(--cyan), transparent);
opacity: 0.6;
}
.topbar-title {
display: flex; align-items: center; gap: 14px;
font-family: var(--font-display);
font-weight: 800; font-size: 1.05rem; letter-spacing: 3px;
color: #fff;
}
.topbar-title .crosshair {
width: 22px; height: 22px;
border: 1.5px solid var(--red); border-radius: 50%;
position: relative; animation: spin 8s linear infinite;
}
.topbar-title .crosshair::before,
.topbar-title .crosshair::after {
content: ''; position: absolute; background: var(--red);
}
.topbar-title .crosshair::before { top: 50%; left: -4px; right: -4px; height: 1px; transform: translateY(-50%); }
.topbar-title .crosshair::after { left: 50%; top: -4px; bottom: -4px; width: 1px; transform: translateX(-50%); }
@keyframes spin { to { transform: rotate(360deg); } }
.topbar-meta {
display: flex; gap: 22px;
font-family: var(--font-mono);
font-size: 0.7rem; color: var(--text-mute);
letter-spacing: 1.5px;
}
.topbar-meta strong { color: var(--cyan); font-weight: 500; }
.topbar-meta .live-dot {
display: inline-block; width: 6px; height: 6px;
border-radius: 50%;
background: var(--green); box-shadow: 0 0 8px var(--green);
margin-right: 6px; animation: pulse 1.2s ease-in-out infinite;
}
.content-area {
flex: 1; overflow-y: auto; padding: 28px;
scroll-behavior: smooth;
scrollbar-width: thin; scrollbar-color: var(--red) transparent;
}
.content-area::-webkit-scrollbar { width: 8px; }
.content-area::-webkit-scrollbar-track { background: transparent; }
.content-area::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, var(--red), var(--cyan));
border-radius: 4px;
}
.tab-content { display: none; animation: tabEnter 480ms cubic-bezier(0.2, 0.8, 0.2, 1); }
.tab-content.active { display: block; }
@keyframes tabEnter {
0% { opacity: 0; transform: translateY(8px) scale(0.99); filter: blur(4px); }
100% { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
}
.hud-panel {
background: var(--panel);
backdrop-filter: blur(18px) saturate(140%);
-webkit-backdrop-filter: blur(18px) saturate(140%);
border: 1px solid var(--panel-edge);
box-shadow: var(--shadow-panel);
padding: 22px; margin-bottom: 22px;
border-radius: var(--radius);
position: relative; overflow: hidden;
transition: var(--transition);
}
.hud-panel::before {
content: ''; position: absolute; inset: 0;
background: var(--grad-panel); opacity: 0.6; pointer-events: none;
}
.hud-panel:hover {
border-color: rgba(255, 255, 255, 0.15);
transform: translateY(-2px);
box-shadow: 0 22px 50px -20px rgba(255, 0, 80, 0.35), var(--shadow-panel);
}
.hud-panel .bracket {
position: absolute; width: 14px; height: 14px;
border: 1.5px solid var(--red);
pointer-events: none; opacity: 0.6;
}
.hud-panel .bracket.tl { top: 6px; left: 6px; border-right: 0; border-bottom: 0; }
.hud-panel .bracket.tr { top: 6px; right: 6px; border-left: 0; border-bottom: 0; }
.hud-panel .bracket.bl { bottom: 6px; left: 6px; border-right: 0; border-top: 0; }
.hud-panel .bracket.br { bottom: 6px; right: 6px; border-left: 0; border-top: 0; }
.panel-title {
font-family: var(--font-display);
font-weight: 700; font-size: 0.9rem; letter-spacing: 2px;
color: var(--text); margin: 0 0 16px;
display: flex; align-items: center; gap: 10px;
text-transform: uppercase;
}
.panel-title::before {
content: ''; width: 3px; height: 14px;
background: var(--grad-hot); border-radius: 2px;
box-shadow: 0 0 10px var(--red);
}
.search-area {
display: flex; gap: 10px; margin-bottom: 18px;
flex-wrap: wrap; align-items: stretch;
}
input, select, textarea {
flex: 1; min-width: 180px;
padding: 13px 18px;
background: rgba(5, 5, 14, 0.7);
border: 1px solid var(--panel-edge);
border-radius: var(--radius-sm);
color: var(--text);
font-family: var(--font-mono);
font-size: 0.88rem; letter-spacing: 0.5px;
outline: none; transition: var(--transition);
}
input::placeholder { color: var(--text-dim); font-family: var(--font-mono); letter-spacing: 1px; }
input:focus, select:focus, textarea:focus {
border-color: var(--red);
background: rgba(5, 5, 14, 0.95);
box-shadow: 0 0 0 2px rgba(255, 0, 80, 0.15), 0 0 24px rgba(255, 0, 80, 0.25);
}
.glitch-btn {
padding: 13px 26px;
background: transparent;
color: var(--cyan);
border: 1px solid var(--cyan);
border-radius: var(--radius-sm);
font-family: var(--font-display);
font-weight: 700; font-size: 0.85rem; letter-spacing: 2px;
cursor: pointer; position: relative; overflow: hidden;
transition: var(--transition); text-transform: uppercase;
box-shadow: inset 0 0 14px rgba(0, 240, 255, 0.1);
z-index: 1;
}
.glitch-btn::before {
content: ''; position: absolute; inset: 0;
background: var(--grad-cool);
transform: scaleX(0); transform-origin: left;
transition: var(--transition); z-index: -1;
}
.glitch-btn:hover::before { transform: scaleX(1); }
.glitch-btn:hover {
color: #000; border-color: transparent;
box-shadow: 0 0 28px rgba(0, 240, 255, 0.55), 0 0 8px rgba(0, 240, 255, 0.35);
transform: translateY(-1px);
}
.glitch-btn:active { transform: translateY(0); }
.glitch-btn.hot {
color: var(--red); border-color: var(--red);
box-shadow: inset 0 0 14px rgba(255, 0, 80, 0.1);
}
.glitch-btn.hot::before { background: var(--grad-hot); }
.glitch-btn.hot:hover { box-shadow: 0 0 28px rgba(255, 0, 80, 0.55); }
.glitch-btn.ok { color: var(--green); border-color: var(--green); }
.glitch-btn.ok::before { background: var(--grad-ok); }
.glitch-btn:disabled { opacity: 0.35; cursor: not-allowed; pointer-events: none; }
.ctrl-btn {
background: rgba(5, 5, 14, 0.6);
color: var(--text);
border: 1px solid var(--panel-edge);
padding: 10px 18px; border-radius: var(--radius-sm);
cursor: pointer;
font-family: var(--font-mono);
font-size: 0.78rem; letter-spacing: 1.5px;
transition: var(--transition);
height: 44px; text-transform: uppercase;
}
.ctrl-btn:hover { background: rgba(255, 0, 80, 0.08); border-color: var(--red); }
.ctrl-btn.stop {
color: var(--red);
border-color: rgba(255, 0, 80, 0.45);
background: rgba(255, 0, 80, 0.05);
}
.ctrl-btn.stop:hover { background: var(--red); color: #fff; box-shadow: 0 0 18px rgba(255, 0, 80, 0.5); }
.ctrl-btn.download {
color: var(--green);
border-color: rgba(0, 255, 140, 0.45);
background: rgba(0, 255, 140, 0.05);
}
.ctrl-btn.download:hover { background: var(--green); color: #000; box-shadow: 0 0 18px rgba(0, 255, 140, 0.5); }
.ctrl-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.controls {
display: flex; gap: 12px; margin-bottom: 18px;
flex-wrap: wrap;
padding-top: 14px; border-top: 1px solid var(--panel-edge);
}
.results, .results-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 14px;
}
.result-card {
background: linear-gradient(160deg, rgba(20, 20, 36, 0.85), rgba(8, 8, 18, 0.85));
border: 1px solid var(--panel-edge);
border-radius: var(--radius);
padding: 18px;
position: relative; overflow: hidden;
transform-style: preserve-3d;
transition: var(--transition);
will-change: transform;
}
.result-card::before {
content: ''; position: absolute;
top: -1px; left: -1px; right: -1px; height: 2px;
background: var(--grad-hot); opacity: 0.7;
}
.result-card::after {
content: ''; position: absolute; inset: 0;
background: radial-gradient(400px circle at var(--mx, 50%) var(--my, 50%), rgba(255, 0, 80, 0.12), transparent 40%);
pointer-events: none; opacity: 0; transition: var(--transition);
}
.result-card:hover {
transform: translateY(-4px);
border-color: rgba(255, 255, 255, 0.18);
box-shadow: 0 18px 40px -16px rgba(255, 0, 80, 0.35);
}
.result-card:hover::after { opacity: 1; }
.result-card .card-title {
font-family: var(--font-display);
font-weight: 700; font-size: 0.9rem; letter-spacing: 1px;
color: var(--text); margin: 0 0 6px;
}
.result-card .card-url {
font-family: var(--font-mono);
font-size: 0.72rem; color: var(--cyan);
text-decoration: none; word-break: break-all;
display: block; margin-bottom: 6px;
}
.result-card .card-url:hover { text-decoration: underline; }
.status-badge {
display: inline-flex; align-items: center; gap: 5px;
padding: 3px 10px;
border: 1px solid currentColor;
border-radius: var(--radius-pill);
font-family: var(--font-mono);
font-size: 0.62rem; letter-spacing: 1.5px;
text-transform: uppercase;
background: rgba(0, 0, 0, 0.4);
color: var(--text-mute);
}
.status-badge::before {
content: ''; width: 5px; height: 5px; border-radius: 50%;
background: currentColor; box-shadow: 0 0 6px currentColor;
}
.status-badge.verified { color: var(--green); }
.status-badge.check { color: var(--amber); animation: pulse 1.2s ease-in-out infinite; }
.status-badge.false { color: var(--red); }
.status-badge.critical { color: var(--red); }
.status-badge.high { color: #ff5e2b; }
.status-badge.medium { color: var(--amber); }
.status-badge.low { color: var(--green); }
.status-badge.clean { color: var(--text-dim); }
.status-badge.info { color: var(--cyan); }
[id$="-status"], .status {
font-family: var(--font-mono);
font-size: 0.78rem; letter-spacing: 1px;
color: var(--text-mute);
padding: 8px 0; min-height: 24px;
display: flex; align-items: center; gap: 8px;
}
[id$="-status"]:not(:empty)::before, .status:not(:empty)::before {
content: '>'; color: var(--red); font-weight: 700;
}
.score-shell {
display: flex; flex-direction: column; gap: 6px;
padding: 14px;
border: 1px solid var(--panel-edge);
border-radius: var(--radius);
background: rgba(5, 5, 14, 0.55);
position: relative; overflow: hidden;
}
.score-bar {
height: 14px;
background: rgba(255, 255, 255, 0.04);
border-radius: var(--radius-pill);
overflow: hidden; position: relative;
}
.score-fill {
height: 100%;
background: var(--grad-warn);
background-size: 200% 100%;
border-radius: var(--radius-pill);
transition: width 1.4s cubic-bezier(0.2, 0.8, 0.2, 1);
animation: gaugeShift 4s linear infinite;
box-shadow: 0 0 14px currentColor;
position: relative;
}
.score-fill::after {
content: ''; position: absolute; inset: 0;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
animation: scoreShine 2.4s linear infinite;
}
@keyframes gaugeShift {
0%, 100% { background-position: 0% 0; }
50% { background-position: 100% 0; }
}
@keyframes scoreShine {
from { transform: translateX(-100%); }
to { transform: translateX(100%); }
}
.score-meta {
display: flex; justify-content: space-between;
font-family: var(--font-mono);
font-size: 0.72rem; letter-spacing: 1.5px;
color: var(--text-mute);
}
.score-meta strong { font-family: var(--font-display); font-size: 1.05rem; color: var(--text); }
.dork-wrapper {
display: flex; align-items: center;
background: rgba(5, 5, 14, 0.6);
border: 1px solid var(--panel-edge);
border-radius: var(--radius-sm);
overflow: hidden; height: 44px;
}
.dork-select {
background: transparent; color: var(--text);
border: none; padding: 0 14px;
font-family: var(--font-mono);
font-size: 0.78rem; letter-spacing: 1px;
outline: none; cursor: pointer;
min-width: auto;
}
.dork-select option { background: var(--bg-1); color: var(--text); }
.dork-btn {
background: rgba(0, 240, 255, 0.1);
color: var(--cyan); border: none;
border-left: 1px solid var(--panel-edge);
padding: 0 16px; height: 100%;
cursor: pointer;
font-family: var(--font-display);
font-weight: 700; font-size: 0.74rem; letter-spacing: 1.5px;
transition: var(--transition);
}
.dork-btn:hover { background: var(--cyan); color: #000; }
.images-container {
display: flex; justify-content: center; gap: 18px;
margin-bottom: 22px; min-height: 180px;
perspective: 1200px; flex-wrap: wrap;
}
.image-wrapper {
position: relative;
width: 190px; height: 190px;
border-radius: var(--radius);
overflow: hidden;
transform-style: preserve-3d;
transition: var(--transition-slow);
border: 1px solid var(--panel-edge);
box-shadow: 0 12px 30px -12px rgba(0, 0, 0, 0.7);
cursor: pointer;
}
.image-wrapper::after {
content: ''; position: absolute; inset: 0;
background: linear-gradient(160deg, transparent 40%, rgba(255, 0, 80, 0.18));
pointer-events: none;
}
.image-wrapper:hover {
transform: translateY(-6px) rotateY(8deg) rotateX(-4deg) scale(1.04);
border-color: var(--red);
box-shadow: 0 24px 48px -12px rgba(255, 0, 80, 0.5);
z-index: 10;
}
.captured-image {
width: 100%; height: 100%;
object-fit: cover; background: #000;
transition: var(--transition);
}
.image-wrapper:hover .captured-image { transform: scale(1.06); filter: contrast(1.1); }
.btn-deep, .dl-btn {
position: absolute;
background: rgba(0, 0, 0, 0.75);
color: var(--cyan);
border: 1px solid var(--cyan);
padding: 5px 10px;
font-family: var(--font-mono);
font-size: 0.65rem; letter-spacing: 1px;
cursor: pointer;
border-radius: var(--radius-sm);
opacity: 0; transition: var(--transition);
}
.image-wrapper:hover .btn-deep,
.image-wrapper:hover .dl-btn { opacity: 1; }
.btn-deep { bottom: 8px; left: 8px; }
.dl-btn { bottom: 8px; right: 8px; color: var(--green); border-color: var(--green); }
.deepModal {
position: fixed; inset: 0;
background: rgba(2, 2, 8, 0.92);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
z-index: 1000;
display: none;
flex-direction: column;
padding: 28px;
}
.deepModal.active { display: flex; animation: tabEnter 320ms ease-out; }
.deepModal h3 {
font-family: var(--font-display);
letter-spacing: 3px; color: var(--cyan); margin: 0 0 18px;
}
.deep-grid {
flex: 1; display: grid;
grid-template-columns: repeat(4, 1fr); gap: 16px;
overflow: hidden;
}
.deep-col {
background: var(--panel);
border: 1px solid var(--panel-edge);
border-radius: var(--radius);
padding: 14px; overflow-y: auto;
}
.deep-col h4 {
margin: 0 0 10px;
font-family: var(--font-display);
font-size: 0.78rem; letter-spacing: 2px;
color: var(--red);
}
.deep-col img {
width: 100%;
border-radius: var(--radius-sm);
margin-bottom: 8px;
border: 1px solid var(--panel-edge);
transition: var(--transition);
}
.deep-col img:hover { transform: scale(1.02); border-color: var(--cyan); }
.target-scan {
color: var(--green); font-family: var(--font-mono);
letter-spacing: 1.5px; animation: pulse 1s ease-in-out infinite;
}
.reveal {
opacity: 0; transform: translateY(14px);
transition: opacity 600ms ease, transform 600ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.reveal.visible { opacity: 1; transform: translateY(0); }
.typing::after {
content: '_'; color: var(--red);
animation: caretBlink 1s steps(2) infinite;
}
@keyframes caretBlink { 50% { opacity: 0; } }
#boot {
position: fixed; inset: 0;
background: #03030a;
z-index: 5000;
display: flex; flex-direction: column; align-items: center; justify-content: center;
gap: 22px;
font-family: var(--font-mono); color: var(--green);
transition: opacity 600ms ease, visibility 0s linear 600ms;
}
#boot.done { opacity: 0; visibility: hidden; }
#boot .boot-logo {
font-family: var(--font-display);
font-size: 2.4rem; letter-spacing: 8px;
background: var(--grad-hot);
-webkit-background-clip: text; background-clip: text; color: transparent;
filter: drop-shadow(0 0 18px rgba(255, 0, 80, 0.5));
animation: pulse 1.6s ease-in-out infinite;
}
#boot .boot-bar {
width: min(420px, 60vw); height: 4px;
border-radius: var(--radius-pill);
background: rgba(255, 255, 255, 0.06); overflow: hidden;
}
#boot .boot-bar > div {
height: 100%; width: 0;
background: var(--grad-cool);
animation: bootLoad 1.6s cubic-bezier(0.65, 0, 0.35, 1) forwards;
}
@keyframes bootLoad { to { width: 100%; } }
#boot .boot-lines {
font-size: 0.74rem; letter-spacing: 1.5px;
color: var(--text-mute); min-height: 110px;
text-align: left; width: min(420px, 60vw);
}
#boot .boot-lines span { display: block; opacity: 0; }
#boot .boot-lines span.show { opacity: 1; transition: opacity 200ms ease; }
#boot .boot-lines .ok { color: var(--green); }
#boot .boot-lines .err { color: var(--amber); }
.row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.col { display: flex; flex-direction: column; gap: 10px; }
.spread { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
.mono { font-family: var(--font-mono); }
.dim { color: var(--text-mute); }
.bright { color: var(--text); }
.h-cyan { color: var(--cyan); }
.h-red { color: var(--red); }
.h-green { color: var(--green); }
.h-amber { color: var(--amber); }
.hide { display: none !important; }
.tag {
display: inline-block;
padding: 3px 9px;
background: rgba(0, 240, 255, 0.08);
border: 1px solid rgba(0, 240, 255, 0.3);
border-radius: var(--radius-sm);
color: var(--cyan);
font-family: var(--font-mono);
font-size: 0.66rem; letter-spacing: 1px;
margin: 2px;
}
.tag.hot { background: rgba(255, 0, 80, 0.08); border-color: rgba(255, 0, 80, 0.35); color: var(--red); }
.tag.ok { background: rgba(0, 255, 140, 0.08); border-color: rgba(0, 255, 140, 0.35); color: var(--green); }
.tag.warn{ background: rgba(255, 184, 0, 0.08); border-color: rgba(255, 184, 0, 0.35); color: var(--amber); }
.mini-bar { height: 6px; background: rgba(255,255,255,0.04); border-radius: var(--radius-pill); overflow: hidden; margin-top: 6px; }
.mini-bar > div { height: 100%; background: var(--grad-cool); border-radius: var(--radius-pill); transition: width 1s ease; }
@media (max-width: 980px) {
.app-container { grid-template-columns: 1fr; }
.sidebar { position: fixed; top: 0; left: -300px; bottom: 0; width: 280px; z-index: 200; transition: left var(--transition); }
.sidebar.open { left: 0; }
}
.leaflet-popup-content-wrapper {
background: var(--panel-2) !important;
color: var(--text) !important;
border: 1px solid var(--panel-edge) !important;
border-radius: var(--radius) !important;
font-family: var(--font-mono) !important;
}
.leaflet-popup-tip { background: var(--panel-2) !important; }
.leaflet-container { background: var(--bg-1) !important; }
================================================
FILE: the_big_brother/gui/static/index.html
================================================
THE BIG BROTHER · V5.0 // GOD'S EYE PROTOCOL
AI Analyst · Cross-Module Synthesis
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.
AUTO
EMAIL
DOMAIN
IP
USERNAME
EXECUTE
Biometric Capture · Visual Intel
0 IMAGES
Auto-pulled from DuckDuckGo · Bing · Google Images · click any thumb for reverse-image search across 4 engines
Target Profiler
INITIATE
[ X ] ABORT
[ ↓ ] EXPORT CSV
LINKEDIN
INSTAGRAM
TWITTER
FACEBOOK
TIKTOK
PINTEREST
GITHUB
GITLAB
STACKOVERFLOW
PASTEBIN
REDDIT
FILETYPE: PDF
FILETYPE: DOC
FILETYPE: TXT
INTEXT SCAN
INTITLE SCAN
INURL SCAN
BREACH CHECK
EMAIL CHECK
PHONE CHECK
DEPLOY ›
SYSTEM STANDBY.
Phantom Identity
HUNT
WAITING FOR TARGET.
Breach Vault
EMAIL
PASSWORD (HASHED)
AUDIT
READY TO CHECK LEAKS.
Mail Tracer · Email Forensics
MX validity · SPF/DMARC posture · disposable/role detection · gravatar lookup · trust score
TRACE
AWAITING TARGET.
Code Hunter · GitHub OSINT
Profile · repos · commit-email harvesting · language stats · activity heatmap
HUNT
READY.
Sigint Sweep
SWEEP
▲ POSITIVE: 0
● NEUTRAL: 0
▼ NEGATIVE: 0
AWAITING DIRECTIVES.
Shadow Map
TRACE
REPUTATION ENGINE NOMINAL.
Paste Dragnet · Multi-Site Hunter
Pastebin · Ghostbin · Rentry · Paste.ee · 0bin · JustPaste · Hastebin · Ideone · DPaste — via DuckDuckGo dorks
DRAGNET
READY TO HUNT.
Dark Web Watch
SEARCH ONION
CAUTION: ONION NETWORK GATEWAY.
Wayback Spectre · Time-Machine Recon
Wayback Machine CDX timeline · year buckets · sensitive-path flagging (admin · .env · .git · backups)
RECALL
READY.
Domain Oracle · Deep Intel
WHOIS (RDAP) · full DNS · SPF/DMARC/DKIM grade · HTTP security headers · TLS · crt.sh subs · reverse-IP
DIVINE
READY.
Network Mapper
MAP NETWORK
READY TO SCAN.
SSL Sentinel
INSPECT SSL
TLS HANDSHAKE PROTOCOL READY.
Crypto Analyzer
BITCOIN
ETHEREUM
ANALYZE
LEDGER CONNECTION ESTABLISHED.
PREMIUM PROJECT CATALOG
UNLOCK ADVANCED CAPABILITIES WITH THESE EXCLUSIVE MODULES.
TheBigBrother God-EYE
PREMIUM
The ultimate evolution of OSINT operations. Direct access to proprietary servers and global data APIs for unprecedented deep-level intelligence.
GET IT
X GodMode
AUTOMATION
Deploy fully automated farms across the X ecosystem — auto-publish, RT, like, comment, engineer sentiment.
GET IT
Amplifior
TIKTOK BOT
Hands-free TikTok empire builder. Autonomous download → edit → publish loops for explosive organic growth.
GET IT
Psychopath
PHISHING
Definitive engineered phishing platform — 100+ targets, SMTP bridging, conversational AI bots.
GET IT
Custom Project
DEVELOPMENT
Arsenal of 200+ operational tools. If a capability doesn't exist, we engineer it. Nothing is impossible.
GET IT
DEEP VISUAL INTEL
[ X ] CLOSE
⚲ SCANNING TARGET ACROSS VISUAL SEARCH ENGINES...
READY
================================================
FILE: the_big_brother/image_grabber.py
================================================
from duckduckgo_search import DDGS
from playwright.sync_api import sync_playwright
import time
import random
def fetch_images_google_playwright(query: str, limit: int = 3, headless: bool = True) -> list[str]:
"""Fallback: Fetch images using Playwright (Google Images)"""
print(f" [+] Attempting Google Images for {query}...")
try:
with sync_playwright() as p:
browser = p.chromium.launch(headless=headless)
context = browser.new_context(
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",
viewport={"width": 1920, "height": 1080},
locale="en-US"
)
page = context.new_page()
# Google Images Search with SafeSearch OFF
page.goto(f"https://www.google.com/search?tbm=isch&q={query}&safe=off", timeout=15000)
# Human-like delay
time.sleep(random.uniform(1.5, 3.0))
# Accept cookies if needed
try:
page.click("button:has-text('Reject all')", timeout=2000)
except: pass
images = page.evaluate("""() => {
const imgs = Array.from(document.querySelectorAll('img'));
return imgs
.map(img => img.src || img.getAttribute('data-src'))
.filter(src => src && src.startsWith('http') && src.length > 50 && !src.includes('googleg') && !src.includes('.svg'))
.slice(0, 5);
}""")
browser.close()
return images[:limit]
except Exception as e:
print(f" [-] Google Playwright error: {e}")
return []
def fetch_images_bing_playwright(query: str, limit: int = 3, headless: bool = True) -> list[str]:
"""Fallback: Fetch images using Playwright (Bing Images)"""
print(f" [+] Attempting Bing Images for {query}...")
try:
with sync_playwright() as p:
browser = p.chromium.launch(headless=headless)
context = browser.new_context(
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",
viewport={"width": 1920, "height": 1080}
)
page = context.new_page()
# Bing Images with SafeSearch OFF
page.goto(f"https://www.bing.com/images/search?q={query}&adlt=off", timeout=15000)
time.sleep(random.uniform(1.0, 2.5))
images = page.evaluate("""() => {
const imgs = Array.from(document.querySelectorAll('.mimg'));
return imgs
.map(img => img.src || img.getAttribute('data-src'))
.filter(src => src && src.startsWith('http'))
.slice(0, 5);
}""")
browser.close()
return images[:limit]
except Exception as e:
print(f" [-] Bing Playwright error: {e}")
return []
def fetch_images(query: str, limit: int = 3) -> list[str]:
"""
Robust Multi-Engine Image Fetcher.
Strategy: DDGS (Fast) -> Bing (Medium) -> Google (Slow/Fallback).
"""
print(f"[*] Starting Image Search for: {query}")
return fetch_images_with_diag(query, limit)[0]
def fetch_images_with_diag(query: str, limit: int = 3):
"""
Same as fetch_images but also returns a diagnostic string describing
which engines were tried and how each failed. Returns (results, diag).
"""
diag_parts: list[str] = []
# 1. DuckDuckGo (no browser needed)
try:
print(" [+] Attempting DuckDuckGo...")
time.sleep(random.uniform(0.5, 1.5))
with DDGS() as ddgs:
ddgs_images = list(ddgs.images(query, max_results=max(limit, 8)))
results = [r['image'] for r in ddgs_images if 'image' in r]
if results:
print(f" [+] DDGS Success: Found {len(results)} images.")
return results[:limit], "DDGS:ok"
diag_parts.append("DDGS:empty")
except Exception as e:
msg = str(e).splitlines()[0][:160]
print(f" [-] DDGS Failed ({msg}). Moving to next engine.")
diag_parts.append(f"DDGS:err({_short_err(msg)})")
# 2. Bing (Playwright)
try:
results = fetch_images_bing_playwright(query, limit)
if results:
print(f" [+] Bing Success: Found {len(results)} images.")
return results, "Bing:ok"
diag_parts.append("Bing:empty")
except Exception as e:
diag_parts.append(f"Bing:err({_short_err(str(e))})")
# 3. Google (Playwright)
try:
results = fetch_images_google_playwright(query, limit)
if results:
print(f" [+] Google Success: Found {len(results)} images.")
return results, "Google:ok"
diag_parts.append("Google:empty")
except Exception as e:
diag_parts.append(f"Google:err({_short_err(str(e))})")
print(" [!] All image fetch methods failed.")
return [], " · ".join(diag_parts)
def _short_err(msg: str) -> str:
"""Map common low-level error strings to human-readable tags."""
s = (msg or "").lower()
if "executable doesn't exist" in s or "chromium" in s and "install" in s:
return "playwright-chromium-missing (run: playwright install chromium)"
if "ratelimit" in s or "rate-limit" in s or "202 ratelimit" in s or "too many" in s:
return "rate-limited"
if "timeout" in s:
return "timeout"
if "executable" in s and "doesn't exist" in s:
return "browser-missing"
return (msg[:80] + "...") if len(msg) > 80 else msg
if __name__ == "__main__":
print(fetch_images("chadi0x"))
================================================
FILE: the_big_brother/modules/__init__.py
================================================
================================================
FILE: the_big_brother/modules/ai_analyst.py
================================================
"""
AI ANALYST — Cross-module orchestrator.
Takes a single target + auto-detected type, fans out to the relevant modules
in parallel, and synthesizes a unified threat profile with a 0-100 score and
narrative bullets. Pure heuristic synthesis — no external LLM required.
"""
import asyncio
import re
from the_big_brother.modules.phantom_id import phantom_id_search
from the_big_brother.modules.breach_vault import breach_vault_search
from the_big_brother.modules.sigint_sweep import sigint_sweep
from the_big_brother.modules.shadow_map import shadow_map_analyze
from the_big_brother.modules.domain_oracle import domain_oracle
from the_big_brother.modules.mail_tracer import mail_tracer
from the_big_brother.modules.code_hunter import code_hunter
from the_big_brother.modules.wayback_spectre import wayback_spectre
from the_big_brother.modules.paste_dragnet import paste_dragnet
from the_big_brother.modules.digital_footprint import run_holehe
EMAIL_RE = re.compile(r"^[^\s@]+@[^\s@]+\.[^\s@]+$")
DOMAIN_RE = re.compile(r"^[a-z0-9.-]+\.[a-z]{2,}$", re.IGNORECASE)
IP_RE = re.compile(r"^\d{1,3}(?:\.\d{1,3}){3}$")
USERNAME_RE = re.compile(r"^@?[a-z0-9_.-]{2,32}$", re.IGNORECASE)
def detect_type(target: str) -> str:
t = target.strip()
if EMAIL_RE.match(t):
return "email"
if IP_RE.match(t):
return "ip"
if DOMAIN_RE.match(t):
return "domain"
if USERNAME_RE.match(t):
return "username"
return "unknown"
async def _safe(coro, label):
try:
return label, await coro
except Exception as e:
return label, {"error": str(e)}
async def ai_analyst(target: str, mode: str = "auto") -> dict:
target = target.strip()
if not target:
return {"error": "target required"}
detected = detect_type(target) if mode == "auto" else mode
if detected == "unknown":
return {"error": "Could not classify target. Try email, domain, IP, or username."}
plan = {
"email": ["mail_tracer", "breach", "holehe", "paste"],
"username": ["phantom", "code_hunter", "paste"],
"domain": ["domain_oracle", "shadow_map", "wayback", "sigint", "paste"],
"ip": ["shadow_map"],
}[detected]
coros = []
if "phantom" in plan:
coros.append(_safe(phantom_id_search(target.lstrip("@")), "phantom"))
if "breach" in plan:
coros.append(_safe(breach_vault_search(target, "email"), "breach"))
if "holehe" in plan:
coros.append(_safe(run_holehe(target), "holehe"))
if "shadow_map" in plan:
coros.append(_safe(shadow_map_analyze(target), "shadow_map"))
if "domain_oracle" in plan:
coros.append(_safe(domain_oracle(target), "domain_oracle"))
if "mail_tracer" in plan:
coros.append(_safe(mail_tracer(target), "mail_tracer"))
if "code_hunter" in plan:
coros.append(_safe(code_hunter(target.lstrip("@")), "code_hunter"))
if "wayback" in plan:
coros.append(_safe(wayback_spectre(target), "wayback"))
if "sigint" in plan:
coros.append(_safe(sigint_sweep(target), "sigint"))
if "paste" in plan:
coros.append(_safe(paste_dragnet(target), "paste"))
results = dict(await asyncio.gather(*coros))
findings = []
score = 0
p = results.get("phantom") or {}
if p.get("found"):
findings.append(f"Found on {p.get('found')}/{p.get('total_checked')} platforms — risk {p.get('risk_score')}")
score += min(30, p.get("risk_score", 0) // 3)
b = results.get("breach") or {}
if b.get("breach_count"):
findings.append(f"Exposed in {b.get('breach_count')} breaches · {b.get('total_records_exposed', 0):,} records")
score += min(35, b.get("breach_count", 0) * 4)
if b.get("pwned"):
findings.append(f"Password seen {b.get('count', 0):,} times in HaveIBeenPwned")
score += 30
s = results.get("shadow_map") or {}
if s.get("threat_score"):
findings.append(f"Threat score {s.get('threat_score')} · {s.get('threat_level')}")
score += min(30, s.get("threat_score", 0) // 3)
d = results.get("domain_oracle") or {}
if d.get("score") is not None:
findings.append(f"Domain hygiene {d.get('score')}/100 — SPF/DMARC/DKIM/headers averaged")
score += max(0, (100 - d.get("score", 100)) // 4)
m = results.get("mail_tracer") or {}
if m.get("trust_level"):
findings.append(f"Email trust: {m.get('trust_level')} ({m.get('trust_score')}/100)")
if m.get("flags", {}).get("disposable"):
score += 15
if not m.get("flags", {}).get("deliverable"):
score += 10
h = results.get("holehe") or {}
if isinstance(h, dict) and h.get("found_on"):
findings.append(f"Email registered on {len(h.get('found_on'))} platforms")
score += min(15, len(h.get("found_on")) * 2)
c = results.get("code_hunter") or {}
if c.get("login") and c.get("public_repos"):
findings.append(f"GitHub: {c.get('public_repos')} repos · {c.get('total_stars', 0)} stars · {len(c.get('commit_emails', []))} commit emails harvested")
if c.get("commit_emails"):
score += 10
w = results.get("wayback") or {}
if w.get("total_snapshots"):
findings.append(f"Wayback: {w.get('total_snapshots'):,} snapshots · {w.get('sensitive_count', 0)} sensitive paths flagged")
score += min(20, w.get("sensitive_count", 0))
pst = results.get("paste") or {}
if pst.get("total_hits"):
critical = sum(1 for r in pst.get("results", []) if r.get("severity") == "CRITICAL")
findings.append(f"Pastes: {pst.get('total_hits')} hits ({critical} flagged critical)")
score += min(25, critical * 5)
sig = results.get("sigint") or {}
if sig.get("total"):
findings.append(f"Sigint: {sig.get('total')} mentions across feeds")
score = max(0, min(100, score))
verdict = (
"CRITICAL" if score >= 75 else
"HIGH" if score >= 50 else
"MODERATE" if score >= 25 else
"LOW"
)
if not findings:
findings.append("No notable signals detected across configured modules.")
return {
"target": target,
"detected_type": detected,
"modules_run": list(results.keys()),
"risk_score": score,
"verdict": verdict,
"findings": findings,
"raw": results,
}
================================================
FILE: the_big_brother/modules/breach_vault.py
================================================
"""
BREACH VAULT — Data breach checker.
Uses HaveIBeenPwned v3 k-anonymity API (no API key needed for passwords,
breaches endpoint uses public API). Also checks paste aggregators.
"""
import asyncio
import aiohttp
import hashlib
import requests
from typing import Optional
HIBP_BREACH_URL = "https://haveibeenpwned.com/api/v3/breachedaccount/{}"
HIBP_PASTE_URL = "https://haveibeenpwned.com/api/v3/pasteaccount/{}"
HIBP_PWNED_URL = "https://api.pwnedpasswords.com/range/{}"
HEADERS = {
"User-Agent": "TheBigBrotherV4-OSINT",
"hibp-api-key": "", # Optional: set via env var HIBP_API_KEY for full access
}
# Paste sites for domain leak scraping
PASTE_SEARCH_URLS = [
"https://psbdmp.ws/api/v3/search/{}", # Pastebin dump search (public)
]
SEVERITY_MAP = {
"Passwords": "CRITICAL",
"Email addresses": "HIGH",
"Phone numbers": "HIGH",
"Physical addresses": "HIGH",
"Credit cards": "CRITICAL",
"Financial data": "CRITICAL",
"Government issued IDs": "CRITICAL",
"Usernames": "MEDIUM",
"Dates of birth": "MEDIUM",
"Social media profiles": "MEDIUM",
"Security questions and answers": "HIGH",
"IP addresses": "LOW",
"Geographic locations": "LOW",
"Browser user agent details": "LOW",
"Website activity": "LOW",
}
def get_severity(data_classes: list) -> str:
"""Determine worst severity from breach data classes."""
order = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]
worst = "LOW"
for dc in data_classes:
sev = SEVERITY_MAP.get(dc, "LOW")
if order.index(sev) < order.index(worst):
worst = sev
return worst
async def check_breaches_hibp(email: str) -> list:
"""Check for breaches using HIBP v3 API."""
import os
api_key = os.environ.get("HIBP_API_KEY", "")
headers = {
"User-Agent": "TheBigBrotherV4-OSINT",
"hibp-api-key": api_key,
}
url = f"https://haveibeenpwned.com/api/v3/breachedaccount/{email}?truncateResponse=false"
try:
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers, timeout=aiohttp.ClientTimeout(total=10), ssl=False) as resp:
if resp.status == 404:
return []
if resp.status == 401:
return [{"_note": "HIBP API key required for breach lookup. Set HIBP_API_KEY env var."}]
if resp.status == 200:
data = await resp.json()
breaches = []
for b in data:
breaches.append({
"name": b.get("Name"),
"domain": b.get("Domain"),
"date": b.get("BreachDate"),
"pwn_count": b.get("PwnCount"),
"description": b.get("Description", "")[:300],
"data_classes": b.get("DataClasses", []),
"severity": get_severity(b.get("DataClasses", [])),
"is_sensitive": b.get("IsSensitive", False),
"is_verified": b.get("IsVerified", True),
"logo": f"https://haveibeenpwned.com/Content/Images/PwnedLogos/{b.get('Name')}.png",
})
return sorted(breaches, key=lambda x: x["date"], reverse=True)
except Exception as e:
print(f"HIBP breach error: {e}")
return []
async def check_pastes_hibp(email: str) -> list:
"""Check for paste exposures via HIBP."""
import os
api_key = os.environ.get("HIBP_API_KEY", "")
if not api_key:
return []
headers = {
"User-Agent": "TheBigBrotherV4-OSINT",
"hibp-api-key": api_key,
}
url = f"https://haveibeenpwned.com/api/v3/pasteaccount/{email}"
try:
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers, timeout=aiohttp.ClientTimeout(total=10), ssl=False) as resp:
if resp.status == 200:
data = await resp.json()
return [{"source": p.get("Source"), "id": p.get("Id"), "date": p.get("Date"), "emails": p.get("EmailCount")} for p in data]
except Exception as e:
print(f"HIBP paste error: {e}")
return []
async def check_password_pwned(password: str) -> dict:
"""
Check if a password has been seen in breaches using k-anonymity model.
Never sends the full password — only first 5 chars of SHA1 hash.
"""
sha1 = hashlib.sha1(password.encode("utf-8")).hexdigest().upper()
prefix = sha1[:5]
suffix = sha1[5:]
url = f"https://api.pwnedpasswords.com/range/{prefix}"
try:
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=8), ssl=False) as resp:
if resp.status == 200:
text = await resp.text()
for line in text.splitlines():
parts = line.split(":")
if len(parts) == 2 and parts[0] == suffix:
return {"pwned": True, "count": int(parts[1])}
except Exception as e:
print(f"HIBP password check error: {e}")
return {"pwned": False, "count": 0}
async def check_paste_aggregator(query: str) -> list:
"""Checks public paste dump APIs for mentions of query (email/domain)."""
results = []
url = f"https://psbdmp.ws/api/v3/search/{query}"
try:
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=8), ssl=False) as resp:
if resp.status == 200:
data = await resp.json()
if isinstance(data, dict) and "data" in data:
for item in data["data"][:10]:
results.append({
"source": "Pastebin",
"id": item.get("id"),
"date": item.get("time"),
"snippet": item.get("tags", ""),
"url": f"https://pastebin.com/{item.get('id')}",
})
except Exception as e:
print(f"Paste aggregator error: {e}")
return results
async def breach_vault_search(query: str, query_type: str = "email"):
"""
Main entry point for BREACH VAULT.
query_type: 'email' | 'password'
"""
if query_type == "password":
result = await check_password_pwned(query)
return {
"query": "***REDACTED***",
"type": "password",
"pwned": result["pwned"],
"count": result["count"],
"breaches": [],
"pastes": [],
}
# Email query
breaches, pastes, paste_dumps = await asyncio.gather(
check_breaches_hibp(query),
check_pastes_hibp(query),
check_paste_aggregator(query),
)
total_exposed = sum(b.get("pwn_count", 0) for b in breaches if isinstance(b.get("pwn_count"), int))
return {
"query": query,
"type": "email",
"breach_count": len(breaches),
"paste_count": len(pastes),
"total_records_exposed": total_exposed,
"breaches": breaches,
"pastes": pastes,
"paste_dumps": paste_dumps,
}
================================================
FILE: the_big_brother/modules/code_hunter.py
================================================
"""
CODE HUNTER — GitHub OSINT via public API (no auth required, rate-limited to 60/hr/IP).
Pulls user profile, repos, top languages, public commit emails, organizations.
"""
from __future__ import annotations
import asyncio
from collections import Counter
from datetime import datetime
from typing import Optional
import requests
API = "https://api.github.com"
HEADERS = {"Accept": "application/vnd.github+json", "User-Agent": "TheBigBrother-V5"}
def _get(path: str, params: Optional[dict] = None):
try:
r = requests.get(f"{API}{path}", headers=HEADERS, params=params or {}, timeout=8)
if r.status_code == 403:
return None, "Rate-limited (60/hr unauthenticated). Add a GITHUB_TOKEN env var to lift the cap."
if r.status_code == 404:
return None, "Not found"
if r.status_code != 200:
return None, f"HTTP {r.status_code}"
return r.json(), None
except Exception as e:
return None, str(e)
def _commit_emails(login: str, repos: list) -> list:
emails = Counter()
for repo in repos[:5]: # Sample top 5 most-recent
data, _ = _get(f"/repos/{login}/{repo['name']}/commits", {"author": login, "per_page": 30})
if not data:
continue
for c in data:
author = (c.get("commit") or {}).get("author") or {}
mail = author.get("email")
if mail and "noreply" not in mail and "users.noreply.github.com" not in mail:
emails[mail] += 1
return [{"email": e, "count": c} for e, c in emails.most_common(10)]
def _activity_buckets(events: list) -> dict:
by_hour = [0] * 24
by_dow = [0] * 7
by_type = Counter()
for e in events or []:
try:
dt = datetime.fromisoformat(e["created_at"].replace("Z", "+00:00"))
by_hour[dt.hour] += 1
by_dow[dt.weekday()] += 1
by_type[e["type"]] += 1
except Exception:
pass
return {
"hourly_utc": by_hour,
"weekday": by_dow,
"event_types": dict(by_type.most_common(10)),
}
async def code_hunter(login: str) -> dict:
login = login.strip().lstrip("@")
if not login:
return {"error": "username required"}
user, err = await asyncio.to_thread(_get, f"/users/{login}")
if err:
return {"error": err}
repos_task = asyncio.to_thread(_get, f"/users/{login}/repos", {"sort": "updated", "per_page": 100})
orgs_task = asyncio.to_thread(_get, f"/users/{login}/orgs")
events_task = asyncio.to_thread(_get, f"/users/{login}/events/public", {"per_page": 100})
gists_task = asyncio.to_thread(_get, f"/users/{login}/gists", {"per_page": 30})
(repos, _), (orgs, _), (events, _), (gists, _) = await asyncio.gather(
repos_task, orgs_task, events_task, gists_task
)
repos = repos or []
orgs = orgs or []
events = events or []
gists = gists or []
repo_summary = sorted(
[
{
"name": r["name"],
"stars": r.get("stargazers_count", 0),
"forks": r.get("forks_count", 0),
"language": r.get("language"),
"updated": r.get("updated_at"),
"description": (r.get("description") or "")[:140],
"url": r.get("html_url"),
"fork": r.get("fork", False),
}
for r in repos
],
key=lambda x: x["stars"],
reverse=True,
)
lang_count = Counter(r["language"] for r in repo_summary if r["language"])
languages = [{"name": k, "count": v} for k, v in lang_count.most_common(10)]
emails = await asyncio.to_thread(_commit_emails, login, repo_summary[:5])
activity = _activity_buckets(events)
total_stars = sum(r["stars"] for r in repo_summary)
score = min(100, user.get("followers", 0) // 2 + total_stars // 5 + len(repo_summary) // 2)
return {
"login": login,
"name": user.get("name"),
"avatar": user.get("avatar_url"),
"bio": user.get("bio"),
"company": user.get("company"),
"location": user.get("location"),
"blog": user.get("blog"),
"twitter": user.get("twitter_username"),
"email_public": user.get("email"),
"created_at": user.get("created_at"),
"updated_at": user.get("updated_at"),
"public_repos": user.get("public_repos"),
"public_gists": user.get("public_gists"),
"followers": user.get("followers"),
"following": user.get("following"),
"html_url": user.get("html_url"),
"influence_score": score,
"total_stars": total_stars,
"top_repos": repo_summary[:12],
"languages": languages,
"orgs": [{"login": o["login"], "url": o.get("url")} for o in orgs[:20]],
"commit_emails": emails,
"gist_count": len(gists),
"activity": activity,
}
================================================
FILE: the_big_brother/modules/crypto_analyzer.py
================================================
import requests
import datetime
def analyze_crypto(address: str, coin: str):
"""
Analyzes a crypto address for balance and activity.
Supports BTC, ETH, LTC, and TRX via free public APIs.
"""
results = {
"coin": coin.upper(),
"address": address,
"balance": 0,
"total_received": 0,
"tx_count": 0,
"last_seen": "Never",
"recent_txs": [],
"error": None
}
try:
if coin.lower() == "btc":
url = f"https://blockchain.info/rawaddr/{address}"
resp = requests.get(url, timeout=10)
if resp.status_code == 200:
data = resp.json()
results["balance"] = data.get("final_balance", 0) / 100000000
results["total_received"] = data.get("total_received", 0) / 100000000
results["tx_count"] = data.get("n_tx", 0)
txs = data.get("txs", [])
if txs:
last_time = txs[0].get("time")
if last_time:
results["last_seen"] = datetime.datetime.fromtimestamp(last_time).strftime('%Y-%m-%d %H:%M:%S')
for t in txs[:5]:
results["recent_txs"].append({
"hash": t.get("hash"),
"time": datetime.datetime.fromtimestamp(t.get("time")).strftime('%Y-%m-%d %H:%M') if t.get("time") else "Unknown",
"result": t.get("result", 0) / 100000000
})
else:
results["error"] = f"API returned {resp.status_code}"
elif coin.lower() == "eth":
url = f"https://api.blockcypher.com/v1/eth/main/addrs/{address}"
resp = requests.get(url, timeout=10)
if resp.status_code == 200:
data = resp.json()
results["balance"] = data.get("balance", 0) / 10**18
results["total_received"] = data.get("total_received", 0) / 10**18
results["tx_count"] = data.get("n_tx", 0)
txrefs = data.get("txrefs", [])
if txrefs:
results["last_seen"] = txrefs[0].get("confirmed", "Check Explorer")[:19].replace("T", " ")
for t in txrefs[:5]:
results["recent_txs"].append({
"hash": t.get("tx_hash"),
"time": t.get("confirmed", "")[:16].replace("T", " "),
"result": t.get("value", 0) / 10**18
})
else:
results["last_seen"] = "Check Explorer"
else:
results["error"] = f"API returned {resp.status_code}"
elif coin.lower() == "ltc":
url = f"https://api.blockcypher.com/v1/ltc/main/addrs/{address}"
resp = requests.get(url, timeout=10)
if resp.status_code == 200:
data = resp.json()
results["balance"] = data.get("balance", 0) / 10**8
results["total_received"] = data.get("total_received", 0) / 10**8
results["tx_count"] = data.get("n_tx", 0)
txrefs = data.get("txrefs", [])
if txrefs:
results["last_seen"] = txrefs[0].get("confirmed", "Check Explorer")[:19].replace("T", " ")
for t in txrefs[:5]:
results["recent_txs"].append({
"hash": t.get("tx_hash"),
"time": t.get("confirmed", "")[:16].replace("T", " "),
"result": t.get("value", 0) / 10**8
})
else:
results["last_seen"] = "Check Explorer"
else:
results["error"] = f"API returned {resp.status_code}"
elif coin.lower() == "trx":
url = f"https://apilist.tronscanapi.com/api/accountv2?address={address}"
resp = requests.get(url, timeout=10)
if resp.status_code == 200:
data = resp.json()
results["balance"] = data.get("balance", 0) / 1000000
results["total_received"] = data.get("totalTransactionCount", 0) # Not exactly total received but useful
results["tx_count"] = data.get("transactions", 0)
last = data.get("latest_operation_time")
if last:
results["last_seen"] = datetime.datetime.fromtimestamp(last/1000).strftime('%Y-%m-%d %H:%M:%S')
else:
results["error"] = f"API returned {resp.status_code}"
except Exception as e:
results["error"] = str(e)
return results
================================================
FILE: the_big_brother/modules/dark_watch.py
================================================
import requests
from bs4 import BeautifulSoup
import asyncio
async def search_ransomware_leaks(query: str):
"""
Searches known ransomware leak sites via Ransomwatch feed.
"""
url = "https://raw.githubusercontent.com/joshhighet/ransomwatch/main/posts.json"
try:
resp = await asyncio.to_thread(requests.get, url, timeout=10)
data = resp.json()
matches = []
query_lower = query.lower()
for post in data:
# post fields: group_name, post_title, discovered, etc.
title = post.get("post_title", "").lower()
group = post.get("group_name", "").lower()
if query_lower in title or query_lower in group:
matches.append({
"title": f"[{post.get('group_name')}] {post.get('post_title')}",
"link": "#", # Usually no direct link in this JSON without digging, but group name is key
"snippet": f"Ransomware Leak Discovered: {post.get('discovered')}",
"date": post.get("discovered")
})
return matches
except Exception as e:
print(f"Ransomwatch error: {e}")
return []
async def search_dark_web(query: str):
"""
Searches Ahmia.fi for onion links AND checks ransomware leaks.
"""
results = []
# 1. Ransomware Search
ransom_results = await search_ransomware_leaks(query)
results.extend(ransom_results)
# 2. Onion Search (Ahmia)
url = f"https://ahmia.fi/search/?q={query}"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"
}
try:
resp = await asyncio.to_thread(requests.get, url, headers=headers, timeout=10)
if resp.status_code == 200:
soup = BeautifulSoup(resp.text, 'html.parser')
for li in soup.find_all('li', class_='result'):
try:
link = li.find('a')['href']
title = li.find('a').text.strip()
snippet = li.find('p').text.strip() if li.find('p') else "No description"
date = li.find('span', class_='modified')
date_str = date.text.strip() if date else "Unknown Date"
results.append({
"title": title,
"link": link,
"snippet": snippet,
"date": date_str
})
except:
continue
return {"results": results, "count": len(results)}
except Exception as e:
# Return what we have if Ahmia fails
if results:
return {"results": results, "count": len(results)}
return {"error": str(e)}
async def check_tor_status(onion_url: str):
"""
Checks if an onion link is live using a tor2web proxy if local Tor isn't available,
or just returns a specific message.
Real Tor checking requires a SOCKS proxy (like 127.0.0.1:9050).
We will assume Tor might not be configured in this Docker container yet,
so we check via a public gateway like onion.ly for basic status.
"""
# Convert .onion to .onion.ly for public check
gateway_url = onion_url.replace(".onion", ".onion.ly")
if not gateway_url.startswith("http"):
gateway_url = "http://" + gateway_url
try:
resp = await asyncio.to_thread(requests.head, gateway_url, timeout=5)
return {"status": "Online" if resp.status_code == 200 else "Offline", "code": resp.status_code}
except:
return {"status": "Unreachable", "code": 0}
================================================
FILE: the_big_brother/modules/digital_footprint.py
================================================
import phonenumbers
from phonenumbers import geocoder, carrier, timezone
import asyncio
import subprocess
import json
import dns.resolver
async def check_email_osint(email: str):
"""
Checks email against holehe's list of sites.
"""
out = []
# Holehe allows checking specific modules or all.
# For speed in this demo, we might want to limit or just run the standard set.
# Using the importable check_email function from holehe
# Note: holehe is primarily CLI based but has core functions.
# We will wrap it.
# Since holehe might be slow, it's best run in a background task or thread.
# Here is a simplified synchronous wrapper that we'll call asynchronously.
from holehe.core import import_submodules
modules = import_submodules("holehe.modules")
results = []
for module in modules:
try:
# Each module has a [module_name] class or function
# This is a simplified integration based on holehe structure
if hasattr(module, str(module.__name__).split(".")[-1]):
check_func = getattr(module, str(module.__name__).split(".")[-1])
# most holehe modules take email, client, out
# We need to inspect holehe source for exact internal API or use CLI wrapper
# For safety and stability, maybe shelling out is safer if internal API is unstable
pass
except Exception:
pass
# Alternative: Shell out to holehe CLI for stability if library use is complex
# But let's try a direct approach if possible or fallback to a simulated "quick check"
# using known patterns if holehe is too heavy.
# REVISION: To ensure this works without deep diving into holehe's internal non-public API,
# let's use a subprocess to call 'holehe' if it's installed as a binary,
# OR better, since we installed it via pip, we can try to use its published entry points.
# Let's implement a robust phone checker first as it is pure library call.
return {"status": "scan_started", "email": email}
def get_phone_info(number_str: str):
try:
parsed_number = phonenumbers.parse(number_str, None)
if not phonenumbers.is_valid_number(parsed_number):
return {"error": "Invalid number"}
# Get line type
line_type_code = phonenumbers.number_type(parsed_number)
line_type_map = {0: "FIXED_LINE", 1: "MOBILE", 2: "FIXED_OR_MOBILE", 3: "TOLL_FREE", 4: "PREMIUM_RATE",
5: "SHARED_COST", 6: "VOIP", 7: "PERSONAL_NUMBER", 8: "PAGER", 9: "UAN", 10: "VOICEMAIL"}
line_type = line_type_map.get(line_type_code, "UNKNOWN")
# Get Timezones
tzs = timezone.time_zones_for_number(parsed_number)
return {
"valid": True,
"number": phonenumbers.format_number(parsed_number, phonenumbers.PhoneNumberFormat.INTERNATIONAL),
"country": geocoder.description_for_number(parsed_number, "en"),
"carrier": carrier.name_for_number(parsed_number, "en"),
"line_type": line_type,
"timezones": list(tzs)
}
except Exception as e:
return {"error": str(e)}
# Re-implementing email check to be robust
import subprocess
import json
async def run_holehe(email: str):
"""
Runs holehe as a subprocess. Also checks MX records.
"""
results = {"email": email, "found_on": [], "mx_records": [], "valid_mx": False}
# 1. MX Record Check
try:
domain = email.split('@')[-1]
mx_records = dns.resolver.resolve(domain, 'MX')
for mx in mx_records:
results["mx_records"].append(str(mx.exchange))
if results["mx_records"]:
results["valid_mx"] = True
except:
pass
# 2. Holehe Scan
cmd = ["holehe", email, "--only-used", "--no-color"]
try:
proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await proc.communicate()
output = stdout.decode()
sites = []
for line in output.splitlines():
if "[+]" in line:
parts = line.split(" ")
site = parts[-1]
if site not in sites:
sites.append(site)
results["found_on"] = sites
except Exception as e:
print(f"Holehe error: {e}")
return results
================================================
FILE: the_big_brother/modules/domain_oracle.py
================================================
"""
DOMAIN ORACLE — Passive deep domain intelligence.
Pulls WHOIS (RDAP), full DNS set, SPF/DMARC/DKIM posture, HTTP security headers,
TLS basic info, and subdomain enumeration via crt.sh — no auth keys required.
"""
import asyncio
import socket
import ssl
import re
from urllib.parse import urlparse
import requests
import dns.resolver
SECURITY_HEADERS = [
"Strict-Transport-Security",
"Content-Security-Policy",
"X-Frame-Options",
"X-Content-Type-Options",
"Referrer-Policy",
"Permissions-Policy",
]
def _resolver():
r = dns.resolver.Resolver()
r.timeout = 2
r.lifetime = 3
return r
def _query(domain: str, rtype: str):
out = []
try:
for rec in _resolver().resolve(domain, rtype):
out.append(str(rec).strip('"'))
except Exception:
pass
return out
def _rdap(domain: str) -> dict:
try:
r = requests.get(f"https://rdap.org/domain/{domain}", timeout=6)
if r.status_code != 200:
return {}
data = r.json()
events = {e.get("eventAction"): e.get("eventDate") for e in data.get("events", [])}
registrar = "Unknown"
for ent in data.get("entities", []):
if "registrar" in ent.get("roles", []):
v = ent.get("vcardArray")
if v and len(v) > 1:
for item in v[1]:
if item[0] == "fn":
registrar = item[3]
break
ns = []
for n in data.get("nameservers", []):
ld = n.get("ldhName")
if ld:
ns.append(ld.lower())
return {
"registrar": registrar,
"status": data.get("status", []),
"created": events.get("registration", "Unknown"),
"updated": events.get("last changed", events.get("last update of RDAP database", "Unknown")),
"expires": events.get("expiration", "Unknown"),
"nameservers": ns,
}
except Exception:
return {}
def _spf(txt_records: list) -> dict:
spf = next((t for t in txt_records if t.lower().startswith("v=spf1")), None)
if not spf:
return {"present": False, "policy": None, "grade": "F"}
policy = "neutral"
if " -all" in spf:
policy = "strict (-all)"
grade = "A"
elif " ~all" in spf:
policy = "softfail (~all)"
grade = "B"
elif " ?all" in spf:
policy = "neutral (?all)"
grade = "C"
elif " +all" in spf:
policy = "pass-all (+all) — DANGEROUS"
grade = "F"
else:
grade = "C"
return {"present": True, "record": spf, "policy": policy, "grade": grade}
def _dmarc(domain: str) -> dict:
recs = _query(f"_dmarc.{domain}", "TXT")
rec = next((r for r in recs if r.lower().startswith("v=dmarc1")), None)
if not rec:
return {"present": False, "grade": "F"}
policy = "none"
if "p=reject" in rec.lower():
policy, grade = "reject", "A"
elif "p=quarantine" in rec.lower():
policy, grade = "quarantine", "B"
else:
policy, grade = "none", "D"
return {"present": True, "record": rec, "policy": policy, "grade": grade}
def _dkim(domain: str) -> dict:
selectors = ["default", "google", "selector1", "selector2", "k1", "mail", "dkim", "smtp"]
found = {}
for s in selectors:
recs = _query(f"{s}._domainkey.{domain}", "TXT")
rec = next((r for r in recs if "v=DKIM" in r or "k=" in r), None)
if rec:
found[s] = rec[:200]
return {
"present": len(found) > 0,
"selectors_found": list(found.keys()),
"samples": found,
"grade": "A" if found else "F",
}
def _http_security_headers(domain: str) -> dict:
out = {"https_reachable": False, "headers": {}, "missing": [], "grade": "F"}
for scheme in ("https", "http"):
try:
r = requests.get(f"{scheme}://{domain}", timeout=6, allow_redirects=True)
present = {}
for h in SECURITY_HEADERS:
if h in r.headers:
present[h] = r.headers[h][:200]
out["https_reachable"] = scheme == "https"
out["status"] = r.status_code
out["server"] = r.headers.get("Server", "—")
out["powered_by"] = r.headers.get("X-Powered-By", "—")
out["headers"] = present
out["missing"] = [h for h in SECURITY_HEADERS if h not in present]
score = len(present) / len(SECURITY_HEADERS)
out["grade"] = (
"A" if score >= 0.83 else
"B" if score >= 0.66 else
"C" if score >= 0.5 else
"D" if score >= 0.33 else "F"
)
return out
except Exception:
continue
return out
def _tls_meta(domain: str) -> dict:
try:
ctx = ssl.create_default_context()
with socket.create_connection((domain, 443), timeout=5) as sock:
with ctx.wrap_socket(sock, server_hostname=domain) as ssock:
cert = ssock.getpeercert()
return {
"tls_version": ssock.version(),
"cipher": ssock.cipher()[0] if ssock.cipher() else None,
"issuer": dict(x[0] for x in cert.get("issuer", [])),
"subject": dict(x[0] for x in cert.get("subject", [])),
"not_before": cert.get("notBefore"),
"not_after": cert.get("notAfter"),
}
except Exception as e:
return {"error": str(e)}
def _subdomains(domain: str) -> list:
try:
r = requests.get(f"https://crt.sh/?q=%.{domain}&output=json", timeout=8)
if r.status_code != 200:
return []
subs = set()
for entry in r.json():
for n in entry.get("name_value", "").split("\n"):
n = n.strip().lower()
if n.endswith(domain) and n != domain and "*" not in n:
subs.add(n)
return sorted(subs)
except Exception:
return []
def _reverse_ip(ip: str) -> list:
try:
r = requests.get(f"https://api.hackertarget.com/reverseiplookup/?q={ip}", timeout=5)
if r.status_code == 200 and "error" not in r.text.lower():
return [x for x in r.text.splitlines() if x.strip()][:50]
except Exception:
pass
return []
async def domain_oracle(domain: str) -> dict:
domain = domain.strip().lower()
if domain.startswith(("http://", "https://")):
domain = urlparse(domain).netloc
if not re.match(r"^[a-z0-9.-]+\.[a-z]{2,}$", domain):
return {"error": "Invalid domain"}
try:
ip = socket.gethostbyname(domain)
except Exception:
return {"error": "Could not resolve domain"}
dns_records = {}
for rtype in ("A", "AAAA", "MX", "NS", "TXT", "CAA", "SOA"):
dns_records[rtype] = await asyncio.to_thread(_query, domain, rtype)
rdap = await asyncio.to_thread(_rdap, domain)
spf = _spf(dns_records.get("TXT", []))
dmarc = await asyncio.to_thread(_dmarc, domain)
dkim = await asyncio.to_thread(_dkim, domain)
headers = await asyncio.to_thread(_http_security_headers, domain)
tls = await asyncio.to_thread(_tls_meta, domain)
subs = await asyncio.to_thread(_subdomains, domain)
neighbors = await asyncio.to_thread(_reverse_ip, ip)
grades = [spf["grade"], dmarc["grade"], dkim["grade"], headers["grade"]]
score = 100 - (sum({"A": 0, "B": 10, "C": 20, "D": 30, "F": 40}[g] for g in grades))
score = max(0, min(100, score))
return {
"domain": domain,
"ip": ip,
"score": score,
"rdap": rdap,
"dns": dns_records,
"email_security": {
"spf": spf,
"dmarc": dmarc,
"dkim": dkim,
},
"http_security": headers,
"tls": tls,
"subdomains": subs,
"neighbors": neighbors,
}
================================================
FILE: the_big_brother/modules/dork_studio.py
================================================
def generate_dorks(target: str, domain: str = ""):
"""
Generates a list of advanced dorks for various platforms.
"""
dorks = {
"google": [],
"shodan": [],
"github": [],
"censys": [],
"fofa": []
}
# ---------------- GOOGLE (AGGRESSIVE) ----------------
# SQL Injection
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"})
# LFI / Directory Traversal
dorks["google"].append({"title": "Directory Traversal", "query": f"site:{domain} inurl:include= | inurl:page= | inurl:file= | inurl:cfg= ext:inc | ext:php"})
# Exposed Git
dorks["google"].append({"title": "Exposed .git", "query": f"site:{domain} intitle:\"index of\" \"/.git\""})
# Public Cameras
dorks["google"].append({"title": "Public Cameras", "query": f"inurl:top.htm inurl:currenttime inurl:pixel"})
# Log Files
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"})
# Install/Setup Pages
dorks["google"].append({"title": "Install/Setup Pages", "query": f"site:{domain} inurl:readme | inurl:license | inurl:install | inurl:setup | inurl:config"})
# S3 Buckets
dorks["google"].append({"title": "S3 Buckets", "query": f"site:s3.amazonaws.com \"{target}\""})
# Pastebin Data
dorks["google"].append({"title": "Pastebin Leaks", "query": f"site:pastebin.com \"{target}\""})
# ---------------- SHODAN (INFRASTRUCTURE) ----------------
dorks["shodan"].append({"title": "Organization Infra", "query": f"org:\"{target}\""})
dorks["shodan"].append({"title": "SSL Certificates", "query": f"ssl:\"{target}\""})
dorks["shodan"].append({"title": "Vulnerable SMB", "query": f"net:\"{target}\" port:445 has_vuln:true"})
dorks["shodan"].append({"title": "Open Webcams", "query": "has_screenshot:true port:80,81,8080 title:\"webcam\""})
dorks["shodan"].append({"title": "Industrial Control Systems", "query": "port:502,102,44818 tag:ics"})
dorks["shodan"].append({"title": "Default Passwords", "query": "\"default password\" \"admin\""})
if domain:
dorks["shodan"].append({"title": "Hostname Search", "query": f"hostname:\"{domain}\""})
# ---------------- GITHUB (SECRETS) ----------------
dorks["github"].append({"title": "AWS Keys", "query": f"\"{target}\" AWS_ACCESS_KEY_ID"})
dorks["github"].append({"title": "API Keys", "query": f"\"{target}\" API_KEY OR SECRET_KEY"})
if domain:
dorks["github"].append({"title": "Internal Passwords", "query": f"\"{domain}\" password OR secret OR key"})
# ---------------- CENSYS ----------------
dorks["censys"].append({"title": "Search by Domain", "query": f"services.tls.certificates.leaf_data.names: {domain}"})
dorks["censys"].append({"title": "Search by Org", "query": f"autonomous_system.name: \"{target}\""})
# ---------------- FOFA ----------------
dorks["fofa"].append({"title": "Domain Search", "query": f"domain=\"{domain}\""})
dorks["fofa"].append({"title": "Title Search", "query": f"title=\"{target}\""})
dorks["fofa"].append({"title": "Cert Search", "query": f"cert=\"{domain}\""})
return dorks
================================================
FILE: the_big_brother/modules/exif_analyzer.py
================================================
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS
import requests
from io import BytesIO
def get_exif_data(image_source: str, is_url: bool = True):
"""
Extracts EXIF data from an image URL or local file (simulated via bytes).
"""
results = {
"source": image_source,
"basic": {},
"gps": {},
"error": None
}
try:
image = None
if is_url:
resp = requests.get(image_source, timeout=10)
if resp.status_code == 200:
image = Image.open(BytesIO(resp.content))
else:
return {"error": f"Failed to download image: {resp.status_code}"}
else:
# For now we only support URL in this quick implementation
return {"error": "Local file upload not implemented in this version"}
if not image:
return {"error": "Could not open image"}
# Basic Info
results["basic"]["format"] = image.format
results["basic"]["mode"] = image.mode
results["basic"]["size"] = f"{image.width}x{image.height}"
exif_data = image._getexif()
if not exif_data:
return results # No exif
for tag_id, value in exif_data.items():
tag = TAGS.get(tag_id, tag_id)
# Decode bytes if needed
if isinstance(value, bytes):
try:
value = value.decode()
except:
value = str(value)
if tag == "GPSInfo":
gps_data = {}
for t in value:
sub_tag = GPSTAGS.get(t, t)
gps_data[sub_tag] = str(value[t])
results["gps"] = gps_data
else:
# Filter out very long binary data (like maker notes)
if len(str(value)) < 500:
results["basic"][tag] = value
except Exception as e:
results["error"] = str(e)
return results
================================================
FILE: the_big_brother/modules/flight_radar.py
================================================
import requests
import datetime
def get_flight_radar(lat: float, lon: float, radius_km: float = 100):
"""
Fetches real-time flight data near the target coordinates.
Uses OpenSky Network API (Free tier).
"""
# 1 degree lat ~ 111km.
deg_diff = radius_km / 111.0
lamin = lat - deg_diff
lamax = lat + deg_diff
lomin = lon - deg_diff
lomax = lon + deg_diff
url = f"https://opensky-network.org/api/states/all?lamin={lamin}&lomin={lomin}&lamax={lamax}&lomax={lomax}"
results = {
"location": {"lat": lat, "lon": lon},
"flights": [],
"count": 0,
"error": None
}
try:
resp = requests.get(url, timeout=10)
if resp.status_code == 200:
data = resp.json()
states = data.get("states", [])
if states:
for s in states[:20]: # Limit to 20 closest/first
# s indexes: 0=icao24, 1=callsign, 2=origin_country, 5=lon, 6=lat, 13=geo_altitude
results["flights"].append({
"icao": s[0],
"callsign": s[1].strip(),
"country": s[2],
"lat": s[6],
"lon": s[5],
"alt": s[13],
"velocity": s[9]
})
results["count"] = len(results["flights"])
else:
results["message"] = "No aircraft found in this sector."
else:
results["error"] = f"Radar Offline: {resp.status_code}"
except Exception as e:
results["error"] = str(e)
return results
================================================
FILE: the_big_brother/modules/geoint_spy.py
================================================
import requests
def get_geoint_data(lat: str, lon: str):
"""
Generates a GEOINT package for the given coordinates.
Includes Sat links, SunCalc, and search queries for social media.
Reverse geocodes coordinates to a physical address.
"""
try:
f_lat = float(lat)
f_lon = float(lon)
except:
return {"error": "Invalid coordinates"}
# Reverse Geocoding (Nominatim OpenStreetMap)
address = "Unknown Address"
try:
headers = {"User-Agent": "TheBigBrotherV4-OSINT"}
resp = requests.get(f"https://nominatim.openstreetmap.org/reverse?format=json&lat={f_lat}&lon={f_lon}", headers=headers, timeout=5)
if resp.status_code == 200:
address = resp.json().get("display_name", "Address not found")
except:
pass
# Google Maps URL
gmaps = f"https://www.google.com/maps/place/{f_lat},{f_lon}/@{f_lat},{f_lon},18z/data=!3m1!1e3"
# Street View
street = f"https://www.google.com/maps?layer=c&cbll={f_lat},{f_lon}"
# SunCalc (Shadow Analysis)
suncalc = f"https://www.suncalc.org/#/{f_lat},{f_lon},18/now"
# Tweet Locator (Twitter advanced search near location)
twitter = f"https://twitter.com/search?q=geocode%3A{f_lat}%2C{f_lon}%2C1km&src=typed_query&f=live"
# Snapchat Map
snapchat = f"https://map.snapchat.com/@{f_lat},{f_lon},15.00z"
# Wikimapia
wikimapia = f"http://wikimapia.org/#lang=en&lat={f_lat}&lon={f_lon}&z=18&m=b"
return {
"coords": f"{f_lat}, {f_lon}",
"address": address,
"links": {
"Google Satellite": gmaps,
"Street View": street,
"SunCalc (Shadows)": suncalc,
"Twitter (Nearby)": twitter,
"Snapchat Map": snapchat,
"Wikimapia": wikimapia
}
}
================================================
FILE: the_big_brother/modules/mail_tracer.py
================================================
"""
MAIL TRACER — Email infrastructure forensics.
Inspects an email address: MX validity, SPF/DMARC presence, disposable/role flags,
gravatar lookup, and computes a deliverability/trust score.
"""
import asyncio
import hashlib
import re
import requests
import dns.resolver
from the_big_brother.modules.domain_oracle import _query, _spf, _dmarc
DISPOSABLE = {
"10minutemail.com", "guerrillamail.com", "mailinator.com", "tempmail.com",
"throwaway.email", "trashmail.com", "yopmail.com", "getairmail.com",
"fakeinbox.com", "sharklasers.com", "tempr.email", "dispostable.com",
"maildrop.cc", "mintemail.com", "mohmal.com", "tempinbox.com",
}
ROLE_LOCALS = {
"admin", "administrator", "info", "support", "help", "contact", "sales",
"noreply", "no-reply", "postmaster", "webmaster", "abuse", "security",
"hr", "jobs", "careers", "marketing", "office", "team", "hello",
}
FREE_PROVIDERS = {
"gmail.com", "yahoo.com", "outlook.com", "hotmail.com", "icloud.com",
"protonmail.com", "proton.me", "tutanota.com", "aol.com", "yandex.com",
"mail.com", "gmx.com", "zoho.com",
}
def _gravatar(email: str) -> str:
h = hashlib.md5(email.strip().lower().encode()).hexdigest()
return f"https://www.gravatar.com/avatar/{h}?s=256&d=404"
def _gravatar_exists(url: str) -> bool:
try:
r = requests.head(url, timeout=5, allow_redirects=False)
return r.status_code == 200
except Exception:
return False
async def mail_tracer(email: str) -> dict:
email = email.strip().lower()
if not re.match(r"^[^\s@]+@[^\s@]+\.[^\s@]+$", email):
return {"error": "Invalid email"}
local, domain = email.rsplit("@", 1)
mx_records = await asyncio.to_thread(_query, domain, "MX")
txt_records = await asyncio.to_thread(_query, domain, "TXT")
a_records = await asyncio.to_thread(_query, domain, "A")
spf = _spf(txt_records)
dmarc = await asyncio.to_thread(_dmarc, domain)
is_disposable = domain in DISPOSABLE
is_role = local in ROLE_LOCALS
is_free = domain in FREE_PROVIDERS
gravatar_url = _gravatar(email)
has_gravatar = await asyncio.to_thread(_gravatar_exists, gravatar_url)
# Trust score
score = 100
factors = []
if not mx_records:
score -= 50
factors.append("No MX records — domain cannot receive mail")
if not a_records and not mx_records:
score -= 20
factors.append("Domain has no DNS presence")
if is_disposable:
score -= 60
factors.append(f"Disposable provider: {domain}")
if is_role:
score -= 15
factors.append(f"Role-based local part: {local}")
if not spf["present"]:
score -= 10
factors.append("No SPF record — spoofable")
elif spf["grade"] in ("C", "F"):
score -= 5
factors.append(f"Weak SPF policy: {spf.get('policy')}")
if not dmarc["present"]:
score -= 10
factors.append("No DMARC record — no enforcement")
elif dmarc["grade"] == "D":
score -= 5
factors.append("DMARC policy is 'none' (monitor only)")
if has_gravatar:
score += 5
factors.append("Gravatar exists — identity tied")
score = max(0, min(100, score))
trust_level = (
"TRUSTED" if score >= 80 else
"MODERATE" if score >= 60 else
"WEAK" if score >= 40 else
"SUSPICIOUS"
)
return {
"email": email,
"local": local,
"domain": domain,
"trust_score": score,
"trust_level": trust_level,
"factors": factors,
"flags": {
"disposable": is_disposable,
"role_based": is_role,
"free_provider": is_free,
"deliverable": bool(mx_records),
},
"mx": mx_records,
"email_security": {"spf": spf, "dmarc": dmarc},
"gravatar": {
"url": gravatar_url if has_gravatar else None,
"exists": has_gravatar,
},
}
================================================
FILE: the_big_brother/modules/network_mapper.py
================================================
import socket
import asyncio
import requests
from pyvis.network import Network
import tempfile
import os
import dns.resolver
COMMON_PORTS = {
21: "FTP", 22: "SSH", 23: "Telnet", 25: "SMTP", 53: "DNS", 80: "HTTP",
110: "POP3", 143: "IMAP", 443: "HTTPS", 465: "SMTPS", 587: "SMTP-MSA",
993: "IMAPS", 995: "POP3S", 1433: "MSSQL", 1521: "Oracle DB",
2082: "cPanel", 2083: "cPanel HTTPS", 3306: "MySQL", 3389: "RDP",
5432: "PostgreSQL", 5900: "VNC", 6379: "Redis", 8080: "HTTP-Alt",
8443: "HTTPS-Alt", 9200: "Elasticsearch", 27017: "MongoDB"
}
async def check_port(ip, port):
conn = asyncio.open_connection(ip, port)
try:
reader, writer = await asyncio.wait_for(conn, timeout=1)
writer.close()
await writer.wait_closed()
return port, True
except:
return port, False
def get_geoip(ip):
try:
resp = requests.get(f"http://ip-api.com/json/{ip}", timeout=5)
if resp.status_code == 200:
return resp.json()
except:
pass
return {}
def get_rdap_whois(domain):
try:
# RDAP is the new JSON standard for WHOIS
resp = requests.get(f"https://rdap.org/domain/{domain}", timeout=5)
if resp.status_code == 200:
data = resp.json()
# Extract key info safely
return {
"registrar": data.get("entities", [{}])[0].get("vcardArray", [[],[]])[1][1][3] if "entities" in data else "Unknown",
"creation_date": next((e["date"] for e in data.get("events", []) if e["eventAction"] == "registration"), "Unknown"),
"status": data.get("status", [])
}
except:
pass
return {}
def get_dns_records(domain):
records = {"MX": [], "NS": [], "TXT": [], "A": []}
try:
resolver = dns.resolver.Resolver()
resolver.timeout = 2
resolver.lifetime = 2
try:
for r in resolver.resolve(domain, 'MX'):
records["MX"].append(str(r.exchange))
except: pass
try:
for r in resolver.resolve(domain, 'NS'):
records["NS"].append(str(r.target))
except: pass
try:
for r in resolver.resolve(domain, 'TXT'):
records["TXT"].append(str(r))
except: pass
try:
for r in resolver.resolve(domain, 'A'):
records["A"].append(str(r.address))
except: pass
except Exception as e:
print(f"DNS Error: {e}")
return records
async def scan_target(domain: str):
"""
Scans a target for IP, open ports, subdomains, GeoIP, Whois, and DNS.
"""
results = {
"domain": domain,
"ip": None,
"ports": [],
"subdomains": [],
"geoip": {},
"whois": {},
"dns": {}
}
# Resolve IP
try:
results["ip"] = socket.gethostbyname(domain)
except:
return {"error": "Could not resolve domain"}
# Parallel Tasks
# 1. Port Scan
port_tasks = [check_port(results["ip"], p) for p in COMMON_PORTS.keys()]
# 2. GeoIP (Sync but fast enough, or thread it)
results["geoip"] = await asyncio.to_thread(get_geoip, results["ip"])
# 3. Whois
results["whois"] = await asyncio.to_thread(get_rdap_whois, domain)
# 4. DNS
results["dns"] = await asyncio.to_thread(get_dns_records, domain)
# 5. Execute Port Scan
port_results = await asyncio.gather(*port_tasks)
for port, is_open in port_results:
if is_open:
results["ports"].append({"port": port, "service": COMMON_PORTS[port]})
# 6. Subdomains (crt.sh)
try:
url = f"https://crt.sh/?q=%.{domain}&output=json"
resp = await asyncio.to_thread(requests.get, url, timeout=5)
if resp.status_code == 200:
data = resp.json()
subs = set()
for entry in data:
name = entry['name_value']
for n in name.split('\n'):
if n.endswith(domain) and n != domain and "*" not in n:
subs.add(n)
results["subdomains"] = list(subs)
except Exception as e:
print(f"Subdomain Error: {e}")
return results
def generate_network_map(data):
"""
Generates an HTML network graph from scan data.
"""
net = Network(height="600px", width="100%", bgcolor="#0a0a0a", font_color="white")
# Root Node
net.add_node(data["domain"], label=data["domain"], color="#00ff41", shape="star", size=30)
# IP Node & Geo
if data.get("ip"):
ip_label = f"{data['ip']}"
if data.get("geoip"):
country = data["geoip"].get("countryCode", "")
isp = data["geoip"].get("isp", "")
ip_label += f"\n[{country}] {isp}"
net.add_node(data["ip"], label=ip_label, color="#ffcc00", shape="diamond")
net.add_edge(data["domain"], data["ip"])
# Ports
for p in data.get("ports", []):
label = f"{p['service']}:{p['port']}"
net.add_node(label, label=label, color="#ff0000", shape="dot", size=10)
net.add_edge(data["ip"], label)
# DNS Nodes (MX, NS)
dns_data = data.get("dns", {})
for mx in dns_data.get("MX", []):
label = f"MX: {mx}"
net.add_node(label, label=label, color="#00ccff", shape="triangle")
net.add_edge(data["domain"], label)
for ns in dns_data.get("NS", []):
label = f"NS: {ns}"
net.add_node(label, label=label, color="#ff00ff", shape="triangle")
net.add_edge(data["domain"], label)
# Subdomains (Cluster them if too many)
subs = data.get("subdomains", [])
if len(subs) > 20:
# Create a cluster node
cluster_label = f"+{len(subs)} SUBDOMAINS"
net.add_node("subs_cluster", label=cluster_label, color="#00cc00", shape="hexagon", size=20)
net.add_edge(data["domain"], "subs_cluster")
# Connect first 5 explicitly
for sub in subs[:5]:
net.add_node(sub, label=sub, color="#00cc00", shape="dot", size=15)
net.add_edge("subs_cluster", sub)
else:
for sub in subs:
net.add_node(sub, label=sub, color="#00cc00", shape="dot", size=15)
net.add_edge(data["domain"], sub)
# Physics options
net.force_atlas_2based()
try:
return net.generate_html()
except:
return "Error generating graph"
================================================
FILE: the_big_brother/modules/paste_dragnet.py
================================================
"""
PASTE DRAGNET — Hunts paste sites for a query using DuckDuckGo dorks.
Falls back gracefully if duckduckgo-search is unavailable.
"""
import asyncio
from urllib.parse import quote
import requests
PASTE_SITES = [
"pastebin.com",
"ghostbin.co",
"rentry.co",
"controlc.com",
"paste.ee",
"0bin.net",
"justpaste.it",
"hastebin.com",
"ideone.com",
"dpaste.org",
"termbin.com",
"privatebin.net",
]
def _ddg_search(query: str, max_results: int = 15):
try:
from duckduckgo_search import DDGS
with DDGS(timeout=10) as ddgs:
return list(ddgs.text(query, max_results=max_results))
except Exception:
return []
def _hunt_site(query: str, site: str):
dork = f'site:{site} "{query}"'
results = _ddg_search(dork, max_results=10)
out = []
for r in results:
out.append({
"title": r.get("title", "")[:160],
"url": r.get("href") or r.get("url"),
"snippet": (r.get("body") or "")[:240],
"site": site,
})
return out
async def paste_dragnet(query: str) -> dict:
query = query.strip()
if not query:
return {"error": "query required"}
tasks = [asyncio.to_thread(_hunt_site, query, site) for site in PASTE_SITES]
results_per_site = await asyncio.gather(*tasks, return_exceptions=True)
all_hits = []
per_site = {}
for site, res in zip(PASTE_SITES, results_per_site):
if isinstance(res, Exception) or not res:
per_site[site] = 0
continue
per_site[site] = len(res)
all_hits.extend(res)
# Severity heuristic: keyword markers commonly seen in dumps
HOT_TERMS = ["password", "passwd", "leak", "dump", "combo", "creds", "api_key", "secret", "token"]
for hit in all_hits:
blob = f"{hit.get('title','')} {hit.get('snippet','')}".lower()
hit["severity"] = "CRITICAL" if any(t in blob for t in HOT_TERMS) else "LOW"
all_hits.sort(key=lambda h: 0 if h["severity"] == "CRITICAL" else 1)
return {
"query": query,
"total_hits": len(all_hits),
"per_site": per_site,
"results": all_hits[:120],
}
================================================
FILE: the_big_brother/modules/phantom_id.py
================================================
"""
PHANTOM ID — Username enumeration across 200+ platforms.
Uses async HTTP with HEAD/GET requests against a curated sites list.
Returns profile URLs, avatar detection hints, and a risk score.
"""
import asyncio
import aiohttp
import hashlib
from typing import Optional
# Curated platform list with detection patterns
PLATFORMS = [
# Social Networks
{"name": "Instagram", "url": "https://www.instagram.com/{}/", "method": "GET", "cat": "social"},
{"name": "Twitter/X", "url": "https://twitter.com/{}", "method": "HEAD", "cat": "social"},
{"name": "TikTok", "url": "https://www.tiktok.com/@{}", "method": "HEAD", "cat": "social"},
{"name": "Facebook", "url": "https://www.facebook.com/{}", "method": "HEAD", "cat": "social"},
{"name": "Pinterest", "url": "https://www.pinterest.com/{}/", "method": "HEAD", "cat": "social"},
{"name": "Snapchat", "url": "https://www.snapchat.com/add/{}", "method": "GET", "not_found_text": "Sorry, we couldn't find that Snapchat account.", "cat": "social"},
{"name": "Reddit", "url": "https://www.reddit.com/user/{}", "method": "GET", "not_found_text": "page not found", "cat": "social"},
{"name": "Tumblr", "url": "https://{}.tumblr.com", "method": "HEAD", "cat": "social"},
{"name": "Discord (Lookup)", "url": "https://discord.com/users/{}", "method": "HEAD", "cat": "social"},
{"name": "VK", "url": "https://vk.com/{}", "method": "HEAD", "cat": "social"},
{"name": "Weibo", "url": "https://weibo.com/{}", "method": "HEAD", "cat": "social"},
{"name": "LinkedIn", "url": "https://www.linkedin.com/in/{}", "method": "HEAD", "cat": "professional"},
# Tech / Dev
{"name": "GitHub", "url": "https://github.com/{}", "method": "GET", "not_found_text": "not found", "cat": "tech"},
{"name": "GitLab", "url": "https://gitlab.com/{}", "method": "HEAD", "cat": "tech"},
{"name": "Bitbucket", "url": "https://bitbucket.org/{}", "method": "HEAD", "cat": "tech"},
{"name": "StackOverflow", "url": "https://stackoverflow.com/users/{}", "method": "HEAD", "cat": "tech"},
{"name": "HackerNews", "url": "https://news.ycombinator.com/user?id={}", "method": "GET", "not_found_text": "No such user", "cat": "tech"},
{"name": "Dev.to", "url": "https://dev.to/{}", "method": "HEAD", "cat": "tech"},
{"name": "Replit", "url": "https://replit.com/@{}", "method": "HEAD", "cat": "tech"},
{"name": "Codepen", "url": "https://codepen.io/{}", "method": "HEAD", "cat": "tech"},
{"name": "Kaggle", "url": "https://www.kaggle.com/{}", "method": "HEAD", "cat": "tech"},
{"name": "Npmjs", "url": "https://www.npmjs.com/~{}", "method": "HEAD", "cat": "tech"},
{"name": "PyPI", "url": "https://pypi.org/user/{}/", "method": "GET", "not_found_text": "404", "cat": "tech"},
# Gaming
{"name": "Steam", "url": "https://steamcommunity.com/id/{}", "method": "GET", "not_found_text": "error", "cat": "gaming"},
{"name": "Twitch", "url": "https://www.twitch.tv/{}", "method": "HEAD", "cat": "gaming"},
{"name": "Xbox", "url": "https://www.xboxgamertag.com/search/{}", "method": "HEAD", "cat": "gaming"},
{"name": "Roblox", "url": "https://www.roblox.com/user.aspx?username={}", "method": "HEAD", "cat": "gaming"},
{"name": "Minecraft (NameMC)", "url": "https://namemc.com/profile/{}", "method": "HEAD", "cat": "gaming"},
{"name": "Chess.com", "url": "https://www.chess.com/member/{}", "method": "HEAD", "cat": "gaming"},
# Content / Creator
{"name": "YouTube", "url": "https://www.youtube.com/@{}", "method": "HEAD", "cat": "content"},
{"name": "Medium", "url": "https://medium.com/@{}", "method": "HEAD", "cat": "content"},
{"name": "Substack", "url": "https://{}.substack.com", "method": "HEAD", "cat": "content"},
{"name": "Patreon", "url": "https://www.patreon.com/{}", "method": "HEAD", "cat": "content"},
{"name": "SoundCloud", "url": "https://soundcloud.com/{}", "method": "HEAD", "cat": "content"},
{"name": "Spotify", "url": "https://open.spotify.com/user/{}", "method": "HEAD", "cat": "content"},
{"name": "Bandcamp", "url": "https://{}.bandcamp.com", "method": "HEAD", "cat": "content"},
{"name": "Flickr", "url": "https://www.flickr.com/people/{}", "method": "HEAD", "cat": "content"},
{"name": "Vimeo", "url": "https://vimeo.com/{}", "method": "HEAD", "cat": "content"},
{"name": "Behance", "url": "https://www.behance.net/{}", "method": "HEAD", "cat": "content"},
{"name": "Dribbble", "url": "https://dribbble.com/{}", "method": "HEAD", "cat": "content"},
{"name": "500px", "url": "https://500px.com/p/{}", "method": "HEAD", "cat": "content"},
# Forums / Community
{"name": "Quora", "url": "https://www.quora.com/profile/{}", "method": "HEAD", "cat": "community"},
{"name": "Disqus", "url": "https://disqus.com/by/{}/", "method": "HEAD", "cat": "community"},
{"name": "ProductHunt", "url": "https://www.producthunt.com/@{}", "method": "HEAD", "cat": "community"},
{"name": "AngelList", "url": "https://angel.co/u/{}", "method": "HEAD", "cat": "professional"},
{"name": "Crunchbase", "url": "https://www.crunchbase.com/person/{}", "method": "HEAD", "cat": "professional"},
# Misc / Identity
{"name": "Gravatar", "url": "https://en.gravatar.com/{}", "method": "HEAD", "cat": "identity"},
{"name": "About.me", "url": "https://about.me/{}", "method": "HEAD", "cat": "identity"},
{"name": "Keybase", "url": "https://keybase.io/{}", "method": "GET", "not_found_text": "not found", "cat": "identity"},
{"name": "Linktree", "url": "https://linktr.ee/{}", "method": "HEAD", "cat": "identity"},
{"name": "Carrd", "url": "https://{}.carrd.co", "method": "HEAD", "cat": "identity"},
]
async def check_platform(session: aiohttp.ClientSession, platform: dict, username: str) -> Optional[dict]:
url = platform["url"].format(username)
method = platform.get("method", "HEAD")
not_found_text = platform.get("not_found_text", "")
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/121.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9",
}
try:
timeout = aiohttp.ClientTimeout(total=8)
if method == "GET" and not_found_text:
async with session.get(url, headers=headers, timeout=timeout, allow_redirects=True, ssl=False) as resp:
if resp.status == 200:
text = await resp.text(errors="ignore")
if not_found_text.lower() in text.lower():
return None
return {"platform": platform["name"], "url": url, "status": resp.status, "cat": platform.get("cat", "misc")}
return None
else:
async with session.head(url, headers=headers, timeout=timeout, allow_redirects=True, ssl=False) as resp:
if resp.status in (200, 301, 302, 307, 308):
return {"platform": platform["name"], "url": url, "status": resp.status, "cat": platform.get("cat", "misc")}
return None
except Exception:
return None
async def phantom_id_search(username: str):
"""
Searches for a username across all configured platforms concurrently.
Returns found profiles, categories, and a risk score.
"""
results = []
connector = aiohttp.TCPConnector(ssl=False, limit=30)
async with aiohttp.ClientSession(connector=connector) as session:
tasks = [check_platform(session, p, username) for p in PLATFORMS]
responses = await asyncio.gather(*tasks, return_exceptions=True)
for r in responses:
if r and isinstance(r, dict):
results.append(r)
# Generate Gravatar avatar URL from username hash
username_hash = hashlib.md5(username.lower().encode()).hexdigest()
gravatar_url = f"https://www.gravatar.com/avatar/{username_hash}?d=404&s=200"
# Risk score: more platforms found = higher exposure score
total = len(PLATFORMS)
found = len(results)
risk_score = round((found / total) * 100, 1)
# Categorize results
cats = {}
for r in results:
c = r.get("cat", "misc")
cats[c] = cats.get(c, 0) + 1
return {
"username": username,
"found": found,
"total_checked": total,
"risk_score": risk_score,
"gravatar": gravatar_url,
"categories": cats,
"profiles": results,
}
================================================
FILE: the_big_brother/modules/shadow_map.py
================================================
"""
SHADOW MAP — IP/Domain threat intelligence and reputation analysis.
Queries AbuseIPDB, VirusTotal (public API), Shodan, and URLhaus.
"""
import asyncio
import aiohttp
import os
import socket
from datetime import datetime
ABUSEIPDB_URL = "https://api.abuseipdb.com/api/v2/check"
VIRUSTOTAL_URL = "https://www.virustotal.com/api/v3/ip_addresses/{}"
VIRUSTOTAL_DOMAIN_URL = "https://www.virustotal.com/api/v3/domains/{}"
URLHAUS_URL = "https://urlhaus-api.abuse.ch/v1/host/"
SHODAN_URL = "https://api.shodan.io/shodan/host/{}?key={}"
IPINFO_URL = "https://ipinfo.io/{}/json"
ABUSE_CATEGORIES = {
1: "DNS Compromise", 2: "DNS Poisoning", 3: "Fraud Orders", 4: "DDoS Attack",
5: "FTP Brute-Force", 6: "Ping of Death", 7: "Phishing", 8: "Fraud VoIP",
9: "Open Proxy", 10: "Web Spam", 11: "Email Spam", 12: "Blog Spam",
13: "VPN IP", 14: "Port Scan", 15: "Hacking", 16: "SQL Injection",
17: "Spoofing", 18: "Brute-Force", 19: "Bad Web Bot", 20: "Exploited Host",
21: "Web App Attack", 22: "SSH", 23: "IoT Targeted",
}
def resolve_to_ip(target: str) -> str:
"""Resolve domain to IP if needed."""
try:
socket.inet_aton(target)
return target # Already an IP
except socket.error:
try:
return socket.gethostbyname(target)
except:
return target
async def check_abuseipdb(session: aiohttp.ClientSession, ip: str) -> dict:
"""Query AbuseIPDB for IP reputation."""
api_key = os.environ.get("ABUSEIPDB_API_KEY", "")
if not api_key:
return {"error": "ABUSEIPDB_API_KEY not set", "available": False}
headers = {"Key": api_key, "Accept": "application/json"}
params = {"ipAddress": ip, "maxAgeInDays": 90, "verbose": ""}
try:
async with session.get(ABUSEIPDB_URL, headers=headers, params=params, timeout=aiohttp.ClientTimeout(total=10), ssl=False) as resp:
if resp.status == 200:
data = (await resp.json()).get("data", {})
category_ids = data.get("reports", [{}])[-1].get("categories", []) if data.get("reports") else []
categories = [ABUSE_CATEGORIES.get(c, f"Category {c}") for c in category_ids]
return {
"available": True,
"ip": ip,
"abuse_score": data.get("abuseConfidenceScore", 0),
"country": data.get("countryCode"),
"isp": data.get("isp"),
"domain": data.get("domain"),
"is_tor": data.get("isTor", False),
"is_whitelisted": data.get("isWhitelisted", False),
"total_reports": data.get("totalReports", 0),
"last_reported": data.get("lastReportedAt"),
"categories": categories,
"usage_type": data.get("usageType"),
}
except Exception as e:
return {"error": str(e), "available": False}
return {"available": False}
async def check_virustotal(session: aiohttp.ClientSession, target: str, is_ip: bool = True) -> dict:
"""Query VirusTotal for IP or domain reputation."""
api_key = os.environ.get("VIRUSTOTAL_API_KEY", "")
if not api_key:
return {"error": "VIRUSTOTAL_API_KEY not set", "available": False}
url = VIRUSTOTAL_URL.format(target) if is_ip else VIRUSTOTAL_DOMAIN_URL.format(target)
headers = {"x-apikey": api_key}
try:
async with session.get(url, headers=headers, timeout=aiohttp.ClientTimeout(total=10), ssl=False) as resp:
if resp.status == 200:
data = (await resp.json()).get("data", {}).get("attributes", {})
stats = data.get("last_analysis_stats", {})
malicious = stats.get("malicious", 0)
suspicious = stats.get("suspicious", 0)
harmless = stats.get("harmless", 0)
undetected = stats.get("undetected", 0)
total = malicious + suspicious + harmless + undetected
return {
"available": True,
"malicious": malicious,
"suspicious": suspicious,
"harmless": harmless,
"undetected": undetected,
"total_engines": total,
"reputation": data.get("reputation", 0),
"tags": data.get("tags", []),
"country": data.get("country"),
"as_owner": data.get("as_owner"),
"threat_level": "CRITICAL" if malicious > 5 else ("HIGH" if malicious > 0 else ("SUSPICIOUS" if suspicious > 0 else "CLEAN")),
}
except Exception as e:
return {"error": str(e), "available": False}
return {"available": False}
async def check_urlhaus(session: aiohttp.ClientSession, target: str) -> dict:
"""Query URLhaus for malicious URL data."""
try:
async with session.post(URLHAUS_URL, data={"host": target}, timeout=aiohttp.ClientTimeout(total=8), ssl=False) as resp:
if resp.status == 200:
data = await resp.json()
if data.get("query_status") == "is_host":
urls = data.get("urls", [])[:5]
return {
"available": True,
"found": True,
"url_count": len(data.get("urls", [])),
"urls": [
{"url": u.get("url"), "status": u.get("url_status"), "threat": u.get("threat"), "date": u.get("date_added")}
for u in urls
],
}
return {"available": True, "found": False}
except Exception as e:
return {"available": True, "found": False, "error": str(e)}
return {"available": False}
async def check_ipinfo(session: aiohttp.ClientSession, ip: str) -> dict:
"""Get basic IP geolocation and ASN info from ipinfo.io (free tier)."""
try:
async with session.get(f"https://ipinfo.io/{ip}/json", timeout=aiohttp.ClientTimeout(total=8), ssl=False) as resp:
if resp.status == 200:
data = await resp.json()
return {
"ip": data.get("ip"),
"city": data.get("city"),
"region": data.get("region"),
"country": data.get("country"),
"org": data.get("org"),
"timezone": data.get("timezone"),
"loc": data.get("loc"),
"hostname": data.get("hostname"),
}
except Exception as e:
return {"error": str(e)}
return {}
async def shadow_map_analyze(target: str):
"""
Main SHADOW MAP entry: analyzes IP or domain for threat intelligence.
"""
# Detect if IP or domain
try:
socket.inet_aton(target)
is_ip = True
ip = target
except socket.error:
is_ip = False
ip = resolve_to_ip(target)
connector = aiohttp.TCPConnector(ssl=False, limit=10)
async with aiohttp.ClientSession(connector=connector) as session:
geo_task = check_ipinfo(session, ip)
abuse_task = check_abuseipdb(session, ip)
vt_task = check_virustotal(session, target, is_ip=is_ip)
urlhaus_task = check_urlhaus(session, target)
geo, abuse, vt, urlhaus = await asyncio.gather(
geo_task, abuse_task, vt_task, urlhaus_task,
return_exceptions=True
)
# Handle exceptions from gather
if isinstance(geo, Exception): geo = {}
if isinstance(abuse, Exception): abuse = {"available": False}
if isinstance(vt, Exception): vt = {"available": False}
if isinstance(urlhaus, Exception): urlhaus = {"available": False}
# Composite threat score (0-100)
score = 0
factors = []
if isinstance(abuse, dict) and abuse.get("available"):
abuse_score = abuse.get("abuse_score", 0)
score += int(abuse_score * 0.5)
if abuse_score > 50:
factors.append(f"HIGH ABUSE SCORE ({abuse_score}%)")
if abuse.get("is_tor"):
score += 20
factors.append("TOR EXIT NODE")
if isinstance(vt, dict) and vt.get("available"):
malicious = vt.get("malicious", 0)
suspicious = vt.get("suspicious", 0)
score += min(malicious * 5 + suspicious * 2, 40)
if malicious > 0:
factors.append(f"{malicious} VT ENGINES FLAGGED MALICIOUS")
if suspicious > 0:
factors.append(f"{suspicious} VT ENGINES FLAGGED SUSPICIOUS")
if isinstance(urlhaus, dict) and urlhaus.get("found"):
score += 15
factors.append(f"FOUND IN URLHAUS ({urlhaus.get('url_count', 0)} MALICIOUS URLS)")
score = min(score, 100)
threat_level = "CRITICAL" if score >= 80 else ("HIGH" if score >= 50 else ("MEDIUM" if score >= 20 else "CLEAN"))
return {
"target": target,
"resolved_ip": ip,
"is_ip": is_ip,
"threat_score": score,
"threat_level": threat_level,
"threat_factors": factors,
"geo": geo if isinstance(geo, dict) else {},
"abuseipdb": abuse if isinstance(abuse, dict) else {},
"virustotal": vt if isinstance(vt, dict) else {},
"urlhaus": urlhaus if isinstance(urlhaus, dict) else {},
}
================================================
FILE: the_big_brother/modules/sigint_sweep.py
================================================
"""
SIGINT SWEEP — Social Intelligence gathering.
Fetches Reddit posts, Google News RSS, and Hacker News mentions
for a given keyword/target. Returns a ranked feed with sentiment hints.
"""
import asyncio
import aiohttp
import xml.etree.ElementTree as ET
from datetime import datetime
import re
POSITIVE_WORDS = {"great", "good", "love", "awesome", "best", "win", "launch", "growth", "success", "excellent", "top", "hire", "partner"}
NEGATIVE_WORDS = {"hack", "breach", "leak", "fail", "bad", "worst", "scam", "fraud", "attack", "lawsuit", "fine", "exposed", "stolen", "malware", "ransomware", "phish"}
def detect_sentiment(text: str) -> str:
text_lower = text.lower()
neg = sum(1 for w in NEGATIVE_WORDS if w in text_lower)
pos = sum(1 for w in POSITIVE_WORDS if w in text_lower)
if neg > pos:
return "NEGATIVE"
if pos > neg:
return "POSITIVE"
return "NEUTRAL"
def clean_html(raw: str) -> str:
clean = re.sub(r'<[^>]+>', '', raw)
clean = re.sub(r'&', '&', clean)
clean = re.sub(r'<', '<', clean)
clean = re.sub(r'>', '>', clean)
clean = re.sub(r'"', '"', clean)
clean = re.sub(r'\s+', ' ', clean).strip()
return clean[:300]
async def fetch_reddit(session: aiohttp.ClientSession, query: str) -> list:
"""Fetch from Reddit's JSON API (no auth needed for public search)."""
results = []
headers = {"User-Agent": "TheBigBrotherV4:OSINT:1.0"}
url = f"https://www.reddit.com/search.json?q={query}&sort=new&limit=15&type=link"
try:
async with session.get(url, headers=headers, timeout=aiohttp.ClientTimeout(total=10), ssl=False) as resp:
if resp.status == 200:
data = await resp.json()
posts = data.get("data", {}).get("children", [])
for post in posts:
p = post.get("data", {})
title = p.get("title", "")
text = p.get("selftext", "")[:200]
results.append({
"source": "Reddit",
"subreddit": f"r/{p.get('subreddit', '')}",
"title": title,
"snippet": text or title,
"url": f"https://reddit.com{p.get('permalink', '')}",
"score": p.get("score", 0),
"comments": p.get("num_comments", 0),
"date": datetime.utcfromtimestamp(p.get("created_utc", 0)).strftime("%Y-%m-%d"),
"sentiment": detect_sentiment(title + " " + text),
})
except Exception as e:
print(f"Reddit fetch error: {e}")
return results
async def fetch_google_news(session: aiohttp.ClientSession, query: str) -> list:
"""Fetch from Google News RSS feed (no auth needed, public)."""
results = []
url = f"https://news.google.com/rss/search?q={query}&hl=en-US&gl=US&ceid=US:en"
try:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=10), ssl=False) as resp:
if resp.status == 200:
text = await resp.text()
root = ET.fromstring(text)
channel = root.find("channel")
if channel is not None:
for item in channel.findall("item")[:15]:
title = item.findtext("title", "")
link = item.findtext("link", "")
pub_date = item.findtext("pubDate", "")
source_el = item.find("source")
source_name = source_el.text if source_el is not None else "News"
description = clean_html(item.findtext("description", ""))
results.append({
"source": "News",
"outlet": source_name,
"title": title,
"snippet": description,
"url": link,
"date": pub_date[:16] if pub_date else "",
"sentiment": detect_sentiment(title + " " + description),
})
except Exception as e:
print(f"Google News fetch error: {e}")
return results
async def fetch_hackernews(session: aiohttp.ClientSession, query: str) -> list:
"""Search Hacker News via Algolia API (public, no auth)."""
results = []
url = f"https://hn.algolia.com/api/v1/search?query={query}&tags=story&hitsPerPage=10"
try:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=8), ssl=False) as resp:
if resp.status == 200:
data = await resp.json()
for hit in data.get("hits", []):
title = hit.get("title", "")
ts = hit.get("created_at", "")[:10]
results.append({
"source": "HackerNews",
"subreddit": "HN",
"title": title,
"snippet": hit.get("story_text", "")[:200] or title,
"url": hit.get("url") or f"https://news.ycombinator.com/item?id={hit.get('objectID')}",
"score": hit.get("points", 0),
"comments": hit.get("num_comments", 0),
"date": ts,
"sentiment": detect_sentiment(title),
})
except Exception as e:
print(f"HackerNews fetch error: {e}")
return results
async def fetch_twitter_nitter(session: aiohttp.ClientSession, query: str) -> list:
"""Fetch recent tweets via public Nitter RSS instances."""
results = []
nitter_instances = [
"https://nitter.poast.org",
"https://nitter.privacydev.net",
"https://nitter.1d4.us",
]
for instance in nitter_instances:
url = f"{instance}/search/rss?q={query}&f=tweets"
try:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=6), ssl=False) as resp:
if resp.status == 200:
text = await resp.text()
root = ET.fromstring(text)
channel = root.find("channel")
if channel is not None:
for item in channel.findall("item")[:10]:
title = item.findtext("title", "")
link = item.findtext("link", "")
pub_date = item.findtext("pubDate", "")
creator = item.findtext("{http://purl.org/dc/elements/1.1/}creator", "")
results.append({
"source": "Twitter/X",
"subreddit": creator,
"title": title,
"snippet": clean_html(title)[:200],
"url": link.replace(instance, "https://twitter.com"),
"score": 0,
"comments": 0,
"date": pub_date[:16] if pub_date else "",
"sentiment": detect_sentiment(title),
})
if results:
break # Got results from this instance
except Exception:
continue
return results
async def sigint_sweep(query: str):
"""
Main SIGINT SWEEP entry point.
Returns merged feed from Reddit, Google News, HackerNews, and Twitter/X.
"""
connector = aiohttp.TCPConnector(ssl=False, limit=20)
async with aiohttp.ClientSession(connector=connector) as session:
reddit_results, news_results, hn_results, twitter_results = await asyncio.gather(
fetch_reddit(session, query),
fetch_google_news(session, query),
fetch_hackernews(session, query),
fetch_twitter_nitter(session, query),
)
all_results = reddit_results + news_results + hn_results + twitter_results
# Sentiment summary
sentiments = {"POSITIVE": 0, "NEGATIVE": 0, "NEUTRAL": 0}
for item in all_results:
sentiments[item.get("sentiment", "NEUTRAL")] += 1
return {
"query": query,
"total": len(all_results),
"sources": {
"reddit": len(reddit_results),
"news": len(news_results),
"hackernews": len(hn_results),
"twitter": len(twitter_results),
},
"sentiment_summary": sentiments,
"feed": all_results,
}
================================================
FILE: the_big_brother/modules/ssl_sentinel.py
================================================
import ssl
import socket
import datetime
def get_ssl_info(domain: str):
"""
Connects to a domain and retrieves SSL certificate details.
"""
ctx = ssl.create_default_context()
results = {
"domain": domain,
"issuer": {},
"subject": {},
"sans": [],
"not_before": "",
"not_after": "",
"expired": False,
"error": None
}
try:
with socket.create_connection((domain, 443), timeout=10) as sock:
with ctx.wrap_socket(sock, server_hostname=domain) as ssock:
cert = ssock.getpeercert()
# Extract Issuer
for item in cert.get('issuer', []):
key, val = item[0]
results['issuer'][key] = val
# Extract Subject
for item in cert.get('subject', []):
key, val = item[0]
results['subject'][key] = val
# Extract SANs (Subject Alternative Names)
# These are gold mines for subdomains
sans = cert.get('subjectAltName', [])
results['sans'] = [val for key, val in sans if key == 'DNS']
# Dates
results['not_before'] = cert.get('notBefore', '')
results['not_after'] = cert.get('notAfter', '')
# Check Expiry
if results['not_after']:
# Format: May 25 12:00:00 2026 GMT
# Python ssl usually returns this format
try:
expire_date = datetime.datetime.strptime(results['not_after'], "%b %d %H:%M:%S %Y %Z")
if expire_date < datetime.datetime.utcnow():
results['expired'] = True
except:
pass
except Exception as e:
results['error'] = str(e)
return results
================================================
FILE: the_big_brother/modules/wayback_spectre.py
================================================
"""
WAYBACK SPECTRE — Wayback Machine CDX timeline + sensitive-path flagging.
Uses the free CDX server to enumerate every archived snapshot, then surfaces
historical paths that often leak data (admin/, .env, .git, backup files, etc).
"""
import asyncio
from collections import Counter
from urllib.parse import urlparse
import requests
SENSITIVE_PATTERNS = [
r"/admin", r"/login", r"/wp-admin", r"/wp-login", r"/phpmyadmin",
r"\.env", r"\.git", r"\.svn", r"\.htaccess", r"\.htpasswd",
r"/config", r"/backup", r"/dump", r"/install", r"/setup",
r"\.bak$", r"\.old$", r"\.sql$", r"\.zip$", r"\.tar\.gz$",
r"/api/internal", r"/debug", r"/test", r"/private", r"/secret",
r"id_rsa", r"\.pem$", r"\.key$", r"credentials", r"/dashboard",
]
def _cdx(domain: str, limit: int = 5000):
url = "https://web.archive.org/cdx/search/cdx"
params = {
"url": f"{domain}/*",
"output": "json",
"limit": limit,
"fl": "timestamp,original,mimetype,statuscode,digest",
"filter": "statuscode:200",
"collapse": "urlkey",
}
try:
r = requests.get(url, params=params, timeout=15)
if r.status_code != 200:
return []
rows = r.json()
if not rows or len(rows) < 2:
return []
header = rows[0]
return [dict(zip(header, row)) for row in rows[1:]]
except Exception:
return []
def _flag_sensitive(rows: list) -> list:
import re
flagged = []
seen = set()
for row in rows:
url = row["original"]
for pat in SENSITIVE_PATTERNS:
if re.search(pat, url, re.IGNORECASE):
key = (url, pat)
if key in seen:
continue
seen.add(key)
ts = row["timestamp"]
flagged.append({
"url": url,
"pattern": pat,
"timestamp": ts,
"snapshot": f"https://web.archive.org/web/{ts}/{url}",
"mime": row.get("mimetype", ""),
})
break
return flagged[:80]
def _yearly_buckets(rows: list) -> list:
by_year = Counter()
for r in rows:
ts = r["timestamp"]
if len(ts) >= 4:
by_year[ts[:4]] += 1
return [{"year": y, "count": c} for y, c in sorted(by_year.items())]
def _mime_buckets(rows: list) -> list:
c = Counter(r.get("mimetype", "unknown") for r in rows)
return [{"mime": m, "count": n} for m, n in c.most_common(10)]
async def wayback_spectre(target: str) -> dict:
target = target.strip()
if target.startswith(("http://", "https://")):
target = urlparse(target).netloc
if not target:
return {"error": "Invalid target"}
rows = await asyncio.to_thread(_cdx, target)
if not rows:
return {
"target": target,
"total_snapshots": 0,
"first_seen": None,
"last_seen": None,
"yearly": [],
"mimes": [],
"sensitive": [],
"sample": [],
"message": "No snapshots found in Wayback Machine.",
}
first = min(rows, key=lambda r: r["timestamp"])
last = max(rows, key=lambda r: r["timestamp"])
sensitive = _flag_sensitive(rows)
yearly = _yearly_buckets(rows)
mimes = _mime_buckets(rows)
sample = []
seen = set()
for r in rows:
u = r["original"]
if u in seen:
continue
seen.add(u)
sample.append({
"url": u,
"timestamp": r["timestamp"],
"snapshot": f"https://web.archive.org/web/{r['timestamp']}/{u}",
})
if len(sample) >= 50:
break
return {
"target": target,
"total_snapshots": len(rows),
"first_seen": first["timestamp"],
"last_seen": last["timestamp"],
"first_url": first["original"],
"yearly": yearly,
"mimes": mimes,
"sensitive_count": len(sensitive),
"sensitive": sensitive,
"sample": sample,
}
================================================
FILE: the_big_brother/notify.py
================================================
"""The Big Brother Notify Module
This module defines the objects for notifying the caller about the
results of queries.
"""
from the_big_brother.result import QueryStatus
from colorama import Fore, Style
import webbrowser
# Global variable to count the number of results.
globvar = 0
class QueryNotify:
"""Query Notify Object.
Base class that describes methods available to notify the results of
a query.
It is intended that other classes inherit from this base class and
override the methods to implement specific functionality.
"""
def __init__(self, result=None):
"""Create Query Notify Object.
Contains information about a specific method of notifying the results
of a query.
Keyword Arguments:
self -- This object.
result -- Object of type QueryResult() containing
results for this query.
Return Value:
Nothing.
"""
self.result = result
# return
def start(self, message=None):
"""Notify Start.
Notify method for start of query. This method will be called before
any queries are performed. This method will typically be
overridden by higher level classes that will inherit from it.
Keyword Arguments:
self -- This object.
message -- Object that is used to give context to start
of query.
Default is None.
Return Value:
Nothing.
"""
# return
def update(self, result):
"""Notify Update.
Notify method for query result. This method will typically be
overridden by higher level classes that will inherit from it.
Keyword Arguments:
self -- This object.
result -- Object of type QueryResult() containing
results for this query.
Return Value:
Nothing.
"""
self.result = result
# return
def finish(self, message=None):
"""Notify Finish.
Notify method for finish of query. This method will be called after
all queries have been performed. This method will typically be
overridden by higher level classes that will inherit from it.
Keyword Arguments:
self -- This object.
message -- Object that is used to give context to start
of query.
Default is None.
Return Value:
Nothing.
"""
# return
def __str__(self):
"""Convert Object To String.
Keyword Arguments:
self -- This object.
Return Value:
Nicely formatted string to get information about this object.
"""
return str(self.result)
class QueryNotifyPrint(QueryNotify):
"""Query Notify Print Object.
Query notify class that prints results.
"""
def __init__(self, result=None, verbose=False, print_all=False, browse=False):
"""Create Query Notify Print Object.
Contains information about a specific method of notifying the results
of a query.
Keyword Arguments:
self -- This object.
result -- Object of type QueryResult() containing
results for this query.
verbose -- Boolean indicating whether to give verbose output.
print_all -- Boolean indicating whether to only print all sites, including not found.
browse -- Boolean indicating whether to open found sites in a web browser.
Return Value:
Nothing.
"""
super().__init__(result)
self.verbose = verbose
self.print_all = print_all
self.browse = browse
return
def start(self, message):
"""Notify Start.
Will print the title to the standard output.
Keyword Arguments:
self -- This object.
message -- String containing username that the series
of queries are about.
Return Value:
Nothing.
"""
title = "Checking username"
print(Style.BRIGHT + Fore.GREEN + "[" +
Fore.YELLOW + "*" +
Fore.GREEN + f"] {title}" +
Fore.WHITE + f" {message}" +
Fore.GREEN + " on:")
# An empty line between first line and the result(more clear output)
print('\r')
return
def countResults(self):
"""This function counts the number of results. Every time the function is called,
the number of results is increasing.
Keyword Arguments:
self -- This object.
Return Value:
The number of results by the time we call the function.
"""
global globvar
globvar += 1
return globvar
def update(self, result):
"""Notify Update.
Will print the query result to the standard output.
Keyword Arguments:
self -- This object.
result -- Object of type QueryResult() containing
results for this query.
Return Value:
Nothing.
"""
self.result = result
response_time_text = ""
if self.result.query_time is not None and self.verbose is True:
response_time_text = f" [{round(self.result.query_time * 1000)}ms]"
# Output to the terminal is desired.
if result.status == QueryStatus.CLAIMED:
self.countResults()
print(Style.BRIGHT + Fore.WHITE + "[" +
Fore.GREEN + "+" +
Fore.WHITE + "]" +
response_time_text +
Fore.GREEN +
f" {self.result.site_name}: " +
Style.RESET_ALL +
f"{self.result.site_url_user}")
if self.browse:
webbrowser.open(self.result.site_url_user, 2)
elif result.status == QueryStatus.AVAILABLE:
if self.print_all:
print(Style.BRIGHT + Fore.WHITE + "[" +
Fore.RED + "-" +
Fore.WHITE + "]" +
response_time_text +
Fore.GREEN + f" {self.result.site_name}:" +
Fore.YELLOW + " Not Found!")
elif result.status == QueryStatus.UNKNOWN:
if self.print_all:
print(Style.BRIGHT + Fore.WHITE + "[" +
Fore.RED + "-" +
Fore.WHITE + "]" +
Fore.GREEN + f" {self.result.site_name}:" +
Fore.RED + f" {self.result.context}" +
Fore.YELLOW + " ")
elif result.status == QueryStatus.ILLEGAL:
if self.print_all:
msg = "Illegal Username Format For This Site!"
print(Style.BRIGHT + Fore.WHITE + "[" +
Fore.RED + "-" +
Fore.WHITE + "]" +
Fore.GREEN + f" {self.result.site_name}:" +
Fore.YELLOW + f" {msg}")
elif result.status == QueryStatus.WAF:
if self.print_all:
print(Style.BRIGHT + Fore.WHITE + "[" +
Fore.RED + "-" +
Fore.WHITE + "]" +
Fore.GREEN + f" {self.result.site_name}:" +
Fore.RED + " Blocked by bot detection" +
Fore.YELLOW + " (proxy may help)")
else:
# It should be impossible to ever get here...
raise ValueError(
f"Unknown Query Status '{result.status}' for site '{self.result.site_name}'"
)
return
def finish(self, message="The processing has been finished."):
"""Notify Start.
Will print the last line to the standard output.
Keyword Arguments:
self -- This object.
message -- The 2 last phrases.
Return Value:
Nothing.
"""
NumberOfResults = self.countResults() - 1
print(Style.BRIGHT + Fore.GREEN + "[" +
Fore.YELLOW + "*" +
Fore.GREEN + "] Search completed with" +
Fore.WHITE + f" {NumberOfResults} " +
Fore.GREEN + "results" + Style.RESET_ALL
)
def __str__(self):
"""Convert Object To String.
Keyword Arguments:
self -- This object.
Return Value:
Nicely formatted string to get information about this object.
"""
return str(self.result)
================================================
FILE: the_big_brother/py.typed
================================================
================================================
FILE: the_big_brother/resources/data.json
================================================
{
"$schema": "data.schema.json",
"1337x": {
"errorMsg": [
"Error something went wrong. ",
"404 Not Found "
],
"errorType": "message",
"regexCheck": "^[A-Za-z0-9]{4,12}$",
"url": "https://www.1337x.to/user/{}/",
"urlMain": "https://www.1337x.to/",
"username_claimed": "FitGirl"
},
"2Dimensions": {
"errorType": "status_code",
"url": "https://2Dimensions.com/a/{}",
"urlMain": "https://2Dimensions.com/",
"username_claimed": "blue"
},
"7Cups": {
"errorType": "status_code",
"url": "https://www.7cups.com/@{}",
"urlMain": "https://www.7cups.com/",
"username_claimed": "blue"
},
"9GAG": {
"errorType": "status_code",
"url": "https://www.9gag.com/u/{}",
"urlMain": "https://www.9gag.com/",
"username_claimed": "blue"
},
"APClips": {
"errorMsg": "Amateur Porn Content Creators",
"errorType": "message",
"isNSFW": true,
"url": "https://apclips.com/{}",
"urlMain": "https://apclips.com/",
"username_claimed": "onlybbyraq"
},
"About.me": {
"errorType": "status_code",
"url": "https://about.me/{}",
"urlMain": "https://about.me/",
"username_claimed": "blue"
},
"Academia.edu": {
"errorType": "status_code",
"regexCheck": "^[^.]*$",
"url": "https://independent.academia.edu/{}",
"urlMain": "https://www.academia.edu/",
"username_claimed": "blue"
},
"AdmireMe.Vip": {
"errorMsg": "Page Not Found",
"errorType": "message",
"isNSFW": true,
"url": "https://admireme.vip/{}",
"urlMain": "https://admireme.vip/",
"username_claimed": "DemiDevil"
},
"Airbit": {
"errorType": "status_code",
"url": "https://airbit.com/{}",
"urlMain": "https://airbit.com/",
"username_claimed": "airbit"
},
"Airliners": {
"errorType": "status_code",
"url": "https://www.airliners.net/user/{}/profile/photos",
"urlMain": "https://www.airliners.net/",
"username_claimed": "yushinlin"
},
"All Things Worn": {
"errorMsg": "Sell Used Panties",
"errorType": "message",
"isNSFW": true,
"url": "https://www.allthingsworn.com/profile/{}",
"urlMain": "https://www.allthingsworn.com",
"username_claimed": "pink"
},
"AllMyLinks": {
"errorMsg": "Page not found",
"errorType": "message",
"regexCheck": "^[a-z0-9][a-z0-9-]{2,32}$",
"url": "https://allmylinks.com/{}",
"urlMain": "https://allmylinks.com/",
"username_claimed": "blue"
},
"AniWorld": {
"errorMsg": "Dieses Profil ist nicht verf\u00fcgbar",
"errorType": "message",
"url": "https://aniworld.to/user/profil/{}",
"urlMain": "https://aniworld.to/",
"username_claimed": "blue"
},
"Anilist": {
"errorType": "status_code",
"regexCheck": "^[A-Za-z0-9]{2,20}$",
"request_method": "POST",
"request_payload": {
"query": "query($name:String){User(name:$name){id}}",
"variables": {
"name": "{}"
}
},
"url": "https://anilist.co/user/{}/",
"urlMain": "https://anilist.co/",
"urlProbe": "https://graphql.anilist.co/",
"username_claimed": "Josh"
},
"Apple Developer": {
"errorType": "status_code",
"url": "https://developer.apple.com/forums/profile/{}",
"urlMain": "https://developer.apple.com",
"username_claimed": "lio24d"
},
"Apple Discussions": {
"errorMsg": "Looking for something in Apple Support Communities?",
"errorType": "message",
"url": "https://discussions.apple.com/profile/{}",
"urlMain": "https://discussions.apple.com",
"username_claimed": "jason"
},
"Aparat": {
"errorType": "status_code",
"request_method": "GET",
"url": "https://www.aparat.com/{}/",
"urlMain": "https://www.aparat.com/",
"urlProbe": "https://www.aparat.com/api/fa/v1/user/user/information/username/{}",
"username_claimed": "jadi"
},
"Archive of Our Own": {
"errorType": "status_code",
"regexCheck": "^[^.]*?$",
"url": "https://archiveofourown.org/users/{}",
"urlMain": "https://archiveofourown.org/",
"username_claimed": "test"
},
"Archive.org": {
"__comment__": "'The resource could not be found' relates to archive downtime",
"errorMsg": [
"could not fetch an account with user item identifier",
"The resource could not be found",
"Internet Archive services are temporarily offline"
],
"errorType": "message",
"url": "https://archive.org/details/@{}",
"urlMain": "https://archive.org",
"urlProbe": "https://archive.org/details/@{}?noscript=true",
"username_claimed": "blue"
},
"Arduino Forum": {
"errorType": "status_code",
"url": "https://forum.arduino.cc/u/{}/summary",
"urlMain": "https://forum.arduino.cc/",
"username_claimed": "system"
},
"ArtStation": {
"errorType": "status_code",
"url": "https://www.artstation.com/{}",
"urlMain": "https://www.artstation.com/",
"username_claimed": "Blue"
},
"Asciinema": {
"errorType": "status_code",
"url": "https://asciinema.org/~{}",
"urlMain": "https://asciinema.org",
"username_claimed": "red"
},
"Ask Fedora": {
"errorType": "status_code",
"url": "https://ask.fedoraproject.org/u/{}",
"urlMain": "https://ask.fedoraproject.org/",
"username_claimed": "red"
},
"Atcoder": {
"errorType": "status_code",
"url": "https://atcoder.jp/users/{}",
"urlMain": "https://atcoder.jp/",
"username_claimed": "ksun48"
},
"Vjudge": {
"errorType": "status_code",
"url": "https://VJudge.net/user/{}",
"urlMain": "https://VJudge.net/",
"username_claimed": "tokitsukaze"
},
"Audiojungle": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z0-9_]+$",
"url": "https://audiojungle.net/user/{}",
"urlMain": "https://audiojungle.net/",
"username_claimed": "blue"
},
"Autofrage": {
"errorType": "status_code",
"url": "https://www.autofrage.net/nutzer/{}",
"urlMain": "https://www.autofrage.net/",
"username_claimed": "autofrage"
},
"Avizo": {
"errorType": "response_url",
"errorUrl": "https://www.avizo.cz/",
"url": "https://www.avizo.cz/{}/",
"urlMain": "https://www.avizo.cz/",
"username_claimed": "blue"
},
"AWS Skills Profile": {
"errorType": "message",
"errorMsg": "shareProfileAccepted\":false",
"url": "https://skillsprofile.skillbuilder.aws/user/{}/",
"urlMain": "https://skillsprofile.skillbuilder.aws",
"username_claimed": "mayank04pant"
},
"BOOTH": {
"errorType": "response_url",
"errorUrl": "https://booth.pm/",
"regexCheck": "^[\\w@-]+?$",
"url": "https://{}.booth.pm/",
"urlMain": "https://booth.pm/",
"username_claimed": "blue"
},
"Bandcamp": {
"errorType": "status_code",
"url": "https://www.bandcamp.com/{}",
"urlMain": "https://www.bandcamp.com/",
"username_claimed": "blue"
},
"Bazar.cz": {
"errorType": "response_url",
"errorUrl": "https://www.bazar.cz/error404.aspx",
"url": "https://www.bazar.cz/{}/",
"urlMain": "https://www.bazar.cz/",
"username_claimed": "pianina"
},
"Behance": {
"errorType": "status_code",
"url": "https://www.behance.net/{}",
"urlMain": "https://www.behance.net/",
"username_claimed": "blue"
},
"Bezuzyteczna": {
"errorType": "status_code",
"url": "https://bezuzyteczna.pl/uzytkownicy/{}",
"urlMain": "https://bezuzyteczna.pl",
"username_claimed": "Jackson"
},
"BiggerPockets": {
"errorType": "status_code",
"url": "https://www.biggerpockets.com/users/{}",
"urlMain": "https://www.biggerpockets.com/",
"username_claimed": "blue"
},
"BioHacking": {
"errorType": "status_code",
"url": "https://forum.dangerousthings.com/u/{}",
"urlMain": "https://forum.dangerousthings.com/",
"username_claimed": "blue"
},
"BitBucket": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z0-9-_]{1,30}$",
"url": "https://bitbucket.org/{}/",
"urlMain": "https://bitbucket.org/",
"username_claimed": "white"
},
"Bitwarden Forum": {
"errorType": "status_code",
"regexCheck": "^(?![.-])[a-zA-Z0-9_.-]{3,20}$",
"url": "https://community.bitwarden.com/u/{}/summary",
"urlMain": "https://bitwarden.com/",
"username_claimed": "blue"
},
"Blipfoto": {
"errorType": "status_code",
"url": "https://www.blipfoto.com/{}",
"urlMain": "https://www.blipfoto.com/",
"username_claimed": "blue"
},
"Blitz Tactics": {
"errorMsg": "That page doesn't exist",
"errorType": "message",
"url": "https://blitztactics.com/{}",
"urlMain": "https://blitztactics.com/",
"username_claimed": "Lance5500"
},
"Blogger": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$",
"url": "https://{}.blogspot.com",
"urlMain": "https://www.blogger.com/",
"username_claimed": "blue"
},
"Bluesky": {
"errorType": "status_code",
"url": "https://bsky.app/profile/{}.bsky.social",
"urlProbe": "https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor={}.bsky.social",
"urlMain": "https://bsky.app/",
"username_claimed": "mcuban"
},
"BongaCams": {
"errorType": "status_code",
"isNSFW": true,
"url": "https://pt.bongacams.com/profile/{}",
"urlMain": "https://pt.bongacams.com",
"username_claimed": "asuna-black"
},
"Bookcrossing": {
"errorType": "status_code",
"url": "https://www.bookcrossing.com/mybookshelf/{}/",
"urlMain": "https://www.bookcrossing.com/",
"username_claimed": "blue"
},
"BoardGameGeek": {
"errorMsg": "\"isValid\":true",
"errorType": "message",
"url": "https://boardgamegeek.com/user/{}",
"urlMain": "https://boardgamegeek.com/",
"urlProbe": "https://api.geekdo.com/api/accounts/validate/username?username={}",
"username_claimed": "blue"
},
"BraveCommunity": {
"errorType": "status_code",
"url": "https://community.brave.com/u/{}/",
"urlMain": "https://community.brave.com/",
"username_claimed": "blue"
},
"BreachSta.rs Forum": {
"errorMsg": "Error - BreachStars ",
"errorType": "message",
"url": "https://breachsta.rs/profile/{}",
"urlMain": "https://breachsta.rs/",
"username_claimed": "Sleepybubble"
},
"BugCrowd": {
"errorType": "status_code",
"url": "https://bugcrowd.com/{}",
"urlMain": "https://bugcrowd.com/",
"username_claimed": "ppfeister"
},
"BuyMeACoffee": {
"errorType": "status_code",
"regexCheck": "[a-zA-Z0-9]{3,15}",
"url": "https://buymeacoff.ee/{}",
"urlMain": "https://www.buymeacoffee.com/",
"urlProbe": "https://www.buymeacoffee.com/{}",
"username_claimed": "red"
},
"BuzzFeed": {
"errorType": "status_code",
"url": "https://buzzfeed.com/{}",
"urlMain": "https://buzzfeed.com/",
"username_claimed": "blue"
},
"Cfx.re Forum": {
"errorType": "status_code",
"url": "https://forum.cfx.re/u/{}/summary",
"urlMain": "https://forum.cfx.re",
"username_claimed": "hightowerlssd"
},
"CGTrader": {
"errorType": "status_code",
"regexCheck": "^[^.]*?$",
"url": "https://www.cgtrader.com/{}",
"urlMain": "https://www.cgtrader.com",
"username_claimed": "blue"
},
"CNET": {
"errorType": "status_code",
"regexCheck": "^[a-z].*$",
"url": "https://www.cnet.com/profiles/{}/",
"urlMain": "https://www.cnet.com/",
"username_claimed": "melliott"
},
"CSSBattle": {
"errorType": "status_code",
"url": "https://cssbattle.dev/player/{}",
"urlMain": "https://cssbattle.dev",
"username_claimed": "beo"
},
"CTAN": {
"errorType": "status_code",
"url": "https://ctan.org/author/{}",
"urlMain": "https://ctan.org/",
"username_claimed": "briggs"
},
"Caddy Community": {
"errorType": "status_code",
"url": "https://caddy.community/u/{}/summary",
"urlMain": "https://caddy.community/",
"username_claimed": "taako_magnusen"
},
"Car Talk Community": {
"errorType": "status_code",
"url": "https://community.cartalk.com/u/{}/summary",
"urlMain": "https://community.cartalk.com/",
"username_claimed": "always_fixing"
},
"Carbonmade": {
"errorType": "response_url",
"errorUrl": "https://carbonmade.com/fourohfour?domain={}.carbonmade.com",
"regexCheck": "^[\\w@-]+?$",
"url": "https://{}.carbonmade.com",
"urlMain": "https://carbonmade.com/",
"username_claimed": "jenny"
},
"Career.habr": {
"errorMsg": "\u041e\u0448\u0438\u0431\u043a\u0430 404 ",
"errorType": "message",
"url": "https://career.habr.com/{}",
"urlMain": "https://career.habr.com/",
"username_claimed": "blue"
},
"CashApp": {
"errorType": "status_code",
"url": "https://cash.app/${}",
"urlMain": "https://cash.app",
"username_claimed": "hotdiggitydog"
},
"Championat": {
"errorType": "status_code",
"url": "https://www.championat.com/user/{}",
"urlMain": "https://www.championat.com/",
"username_claimed": "blue"
},
"Chaos": {
"errorType": "status_code",
"url": "https://chaos.social/@{}",
"urlMain": "https://chaos.social/",
"username_claimed": "ordnung"
},
"Chatujme.cz": {
"errorMsg": "Neexistujic\u00ed profil",
"errorType": "message",
"regexCheck": "^[a-zA-Z][a-zA-Z1-9_-]*$",
"url": "https://profil.chatujme.cz/{}",
"urlMain": "https://chatujme.cz/",
"username_claimed": "david"
},
"ChaturBate": {
"errorType": "status_code",
"isNSFW": true,
"url": "https://chaturbate.com/{}",
"urlMain": "https://chaturbate.com",
"username_claimed": "cute18cute"
},
"Chess": {
"errorMsg": "Username is valid",
"errorType": "message",
"regexCheck": "^[a-z1-9]{3,25}$",
"url": "https://www.chess.com/member/{}",
"urlMain": "https://www.chess.com/",
"urlProbe": "https://www.chess.com/callback/user/valid?username={}",
"username_claimed": "blue"
},
"Choice Community": {
"errorType": "status_code",
"url": "https://choice.community/u/{}/summary",
"urlMain": "https://choice.community/",
"username_claimed": "gordon"
},
"Clapper": {
"errorType": "status_code",
"url": "https://clapperapp.com/{}",
"urlMain": "https://clapperapp.com/",
"username_claimed": "blue"
},
"CloudflareCommunity": {
"errorType": "status_code",
"url": "https://community.cloudflare.com/u/{}",
"urlMain": "https://community.cloudflare.com/",
"username_claimed": "blue"
},
"Clozemaster": {
"errorMsg": "Oh no! Player not found.",
"errorType": "message",
"url": "https://www.clozemaster.com/players/{}",
"urlMain": "https://www.clozemaster.com",
"username_claimed": "green"
},
"Clubhouse": {
"errorType": "status_code",
"url": "https://www.clubhouse.com/@{}",
"urlMain": "https://www.clubhouse.com",
"username_claimed": "waniathar"
},
"Code Snippet Wiki": {
"errorMsg": "This user has not filled out their profile page yet",
"errorType": "message",
"url": "https://codesnippets.fandom.com/wiki/User:{}",
"urlMain": "https://codesnippets.fandom.com",
"username_claimed": "bob"
},
"Codeberg": {
"errorType": "status_code",
"url": "https://codeberg.org/{}",
"urlMain": "https://codeberg.org/",
"username_claimed": "blue"
},
"Codecademy": {
"errorMsg": "This profile could not be found",
"errorType": "message",
"url": "https://www.codecademy.com/profiles/{}",
"urlMain": "https://www.codecademy.com/",
"username_claimed": "blue"
},
"Codechef": {
"errorType": "response_url",
"errorUrl": "https://www.codechef.com/",
"url": "https://www.codechef.com/users/{}",
"urlMain": "https://www.codechef.com/",
"username_claimed": "blue"
},
"Codeforces": {
"errorType": "status_code",
"url": "https://codeforces.com/profile/{}",
"urlMain": "https://codeforces.com/",
"urlProbe": "https://codeforces.com/api/user.info?handles={}",
"username_claimed": "tourist"
},
"Codepen": {
"errorType": "status_code",
"url": "https://codepen.io/{}",
"urlMain": "https://codepen.io/",
"username_claimed": "blue"
},
"Coders Rank": {
"errorMsg": "not a registered member",
"errorType": "message",
"regexCheck": "^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$",
"url": "https://profile.codersrank.io/user/{}/",
"urlMain": "https://codersrank.io/",
"username_claimed": "rootkit7628"
},
"Coderwall": {
"errorType": "status_code",
"url": "https://coderwall.com/{}",
"urlMain": "https://coderwall.com",
"username_claimed": "hacker"
},
"CodeSandbox": {
"errorType": "message",
"errorMsg": "Could not find user with username",
"regexCheck": "^[a-zA-Z0-9_-]{3,30}$",
"url": "https://codesandbox.io/u/{}",
"urlProbe": "https://codesandbox.io/api/v1/users/{}",
"urlMain": "https://codesandbox.io",
"username_claimed": "icyjoseph"
},
"Codewars": {
"errorType": "status_code",
"url": "https://www.codewars.com/users/{}",
"urlMain": "https://www.codewars.com",
"username_claimed": "example"
},
"Codolio": {
"errorType": "message",
"errorMsg": "Page Not Found | Codolio ",
"url": "https://codolio.com/profile/{}",
"urlMain": "https://codolio.com/",
"username_claimed": "testuser",
"regexCheck": "^[a-zA-Z0-9_-]{3,30}$"
},
"Coinvote": {
"errorType": "status_code",
"url": "https://coinvote.cc/profile/{}",
"urlMain": "https://coinvote.cc/",
"username_claimed": "blue"
},
"ColourLovers": {
"errorType": "status_code",
"url": "https://www.colourlovers.com/lover/{}",
"urlMain": "https://www.colourlovers.com/",
"username_claimed": "blue"
},
"Contently": {
"errorType": "response_url",
"errorUrl": "https://contently.com",
"regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$",
"url": "https://{}.contently.com/",
"urlMain": "https://contently.com/",
"username_claimed": "jordanteicher"
},
"Coroflot": {
"errorType": "status_code",
"url": "https://www.coroflot.com/{}",
"urlMain": "https://coroflot.com/",
"username_claimed": "blue"
},
"Cplusplus": {
"errorType": "message",
"errorMsg": "404 Page Not Found ",
"url": "https://cplusplus.com/user/{}",
"urlMain": "https://cplusplus.com",
"username_claimed": "mbozzi"
},
"Cracked": {
"errorType": "response_url",
"errorUrl": "https://www.cracked.com/",
"url": "https://www.cracked.com/members/{}/",
"urlMain": "https://www.cracked.com/",
"username_claimed": "blue"
},
"Cracked Forum": {
"errorMsg": "The member you specified is either invalid or doesn't exist",
"errorType": "message",
"url": "https://cracked.sh/{}",
"urlMain": "https://cracked.sh/",
"username_claimed": "Blue"
},
"Crevado": {
"errorType": "status_code",
"regexCheck": "^[\\w@-]+?$",
"url": "https://{}.crevado.com",
"urlMain": "https://crevado.com/",
"username_claimed": "blue"
},
"Crowdin": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z0-9._-]{2,255}$",
"url": "https://crowdin.com/profile/{}",
"urlMain": "https://crowdin.com/",
"username_claimed": "blue"
},
"CryptoHack": {
"errorType": "response_url",
"errorUrl": "https://cryptohack.org/",
"url": "https://cryptohack.org/user/{}/",
"urlMain": "https://cryptohack.org/",
"username_claimed": "blue"
},
"Cryptomator Forum": {
"errorType": "status_code",
"url": "https://community.cryptomator.org/u/{}",
"urlMain": "https://community.cryptomator.org/",
"username_claimed": "michael"
},
"Cults3D": {
"errorMsg": "Oh dear, this page is not working!",
"errorType": "message",
"url": "https://cults3d.com/en/users/{}/creations",
"urlMain": "https://cults3d.com/en",
"username_claimed": "brown"
},
"CyberDefenders": {
"errorType": "status_code",
"regexCheck": "^[^\\/:*?\"<>|@]{3,50}$",
"request_method": "GET",
"url": "https://cyberdefenders.org/p/{}",
"urlMain": "https://cyberdefenders.org/",
"username_claimed": "mlohn"
},
"DEV Community": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$",
"url": "https://dev.to/{}",
"urlMain": "https://dev.to/",
"username_claimed": "blue"
},
"DMOJ": {
"errorMsg": "No such user",
"errorType": "message",
"url": "https://dmoj.ca/user/{}",
"urlMain": "https://dmoj.ca/",
"username_claimed": "junferno"
},
"DailyMotion": {
"errorType": "status_code",
"url": "https://www.dailymotion.com/{}",
"urlMain": "https://www.dailymotion.com/",
"username_claimed": "blue"
},
"dcinside": {
"errorType": "status_code",
"url": "https://gallog.dcinside.com/{}",
"urlMain": "https://www.dcinside.com/",
"username_claimed": "anrbrb"
},
"Dealabs": {
"errorMsg": "La page que vous essayez",
"errorType": "message",
"regexCheck": "[a-z0-9]{4,16}",
"url": "https://www.dealabs.com/profile/{}",
"urlMain": "https://www.dealabs.com/",
"username_claimed": "blue"
},
"DeviantArt": {
"errorType": "message",
"errorMsg": "Llama Not Found",
"regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$",
"url": "https://www.deviantart.com/{}",
"urlMain": "https://www.deviantart.com/",
"username_claimed": "blue"
},
"DigitalSpy": {
"errorMsg": "The page you were looking for could not be found.",
"errorType": "message",
"url": "https://forums.digitalspy.com/profile/{}",
"urlMain": "https://forums.digitalspy.com/",
"username_claimed": "blue",
"regexCheck": "^\\w{3,20}$"
},
"Discogs": {
"errorType": "status_code",
"url": "https://www.discogs.com/user/{}",
"urlMain": "https://www.discogs.com/",
"username_claimed": "blue"
},
"Discord": {
"errorType": "message",
"url": "https://discord.com",
"urlMain": "https://discord.com/",
"urlProbe": "https://discord.com/api/v9/unique-username/username-attempt-unauthed",
"errorMsg": ["{\"taken\":false}", "The resource is being rate limited"],
"request_method": "POST",
"request_payload": {
"username": "{}"
},
"headers": {
"Content-Type": "application/json"
},
"username_claimed": "blue"
},
"Discord.bio": {
"errorType": "message",
"errorMsg": "Server Error (500) ",
"url": "https://discords.com/api-v2/bio/details/{}",
"urlMain": "https://discord.bio/",
"username_claimed": "robert"
},
"Discuss.Elastic.co": {
"errorType": "status_code",
"url": "https://discuss.elastic.co/u/{}",
"urlMain": "https://discuss.elastic.co/",
"username_claimed": "blue"
},
"Diskusjon.no": {
"errorMsg": "{\"result\":\"ok\"}",
"errorType": "message",
"regexCheck": "^[a-zA-Z0-9_.-]{3,40}$",
"urlProbe": "https://www.diskusjon.no/?app=core&module=system&controller=ajax&do=usernameExists&input={}",
"url": "https://www.diskusjon.no",
"urlMain": "https://www.diskusjon.no",
"username_claimed": "blue"
},
"Disqus": {
"errorType": "status_code",
"url": "https://disqus.com/{}",
"urlMain": "https://disqus.com/",
"username_claimed": "blue"
},
"Docker Hub": {
"errorType": "status_code",
"url": "https://hub.docker.com/u/{}/",
"urlMain": "https://hub.docker.com/",
"urlProbe": "https://hub.docker.com/v2/users/{}/",
"username_claimed": "blue"
},
"Dribbble": {
"errorMsg": "Whoops, that page is gone.",
"errorType": "message",
"regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$",
"url": "https://dribbble.com/{}",
"urlMain": "https://dribbble.com/",
"username_claimed": "blue"
},
"Duolingo": {
"errorMsg": "{\"users\":[]}",
"errorType": "message",
"url": "https://www.duolingo.com/profile/{}",
"urlMain": "https://duolingo.com/",
"urlProbe": "https://www.duolingo.com/2017-06-30/users?username={}",
"username_claimed": "blue"
},
"Eintracht Frankfurt Forum": {
"errorType": "status_code",
"regexCheck": "^[^.]*?$",
"url": "https://community.eintracht.de/fans/{}",
"urlMain": "https://community.eintracht.de/",
"username_claimed": "mmammu"
},
"Empretienda AR": {
"__comment__": "Note that Error Connecting responses may be indicative of unclaimed handles",
"errorType": "status_code",
"url": "https://{}.empretienda.com.ar",
"urlMain": "https://empretienda.com",
"username_claimed": "camalote"
},
"Envato Forum": {
"errorType": "status_code",
"url": "https://forums.envato.com/u/{}",
"urlMain": "https://forums.envato.com/",
"username_claimed": "enabled"
},
"Erome": {
"errorType": "status_code",
"isNSFW": true,
"url": "https://www.erome.com/{}",
"urlMain": "https://www.erome.com/",
"username_claimed": "bob"
},
"Exposure": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z0-9-]{1,63}$",
"url": "https://{}.exposure.co/",
"urlMain": "https://exposure.co/",
"username_claimed": "jonasjacobsson"
},
"exophase": {
"errorType": "status_code",
"url": "https://www.exophase.com/user/{}/",
"urlMain": "https://www.exophase.com/",
"username_claimed": "blue"
},
"EyeEm": {
"errorType": "status_code",
"url": "https://www.eyeem.com/u/{}",
"urlMain": "https://www.eyeem.com/",
"username_claimed": "blue"
},
"F3.cool": {
"errorType": "status_code",
"url": "https://f3.cool/{}/",
"urlMain": "https://f3.cool/",
"username_claimed": "blue"
},
"Fameswap": {
"errorType": "status_code",
"url": "https://fameswap.com/user/{}",
"urlMain": "https://fameswap.com/",
"username_claimed": "fameswap"
},
"Fandom": {
"errorType": "status_code",
"url": "https://www.fandom.com/u/{}",
"urlMain": "https://www.fandom.com/",
"username_claimed": "Jungypoo"
},
"Fanpop": {
"errorType": "response_url",
"errorUrl": "https://www.fanpop.com/",
"url": "https://www.fanpop.com/fans/{}",
"urlMain": "https://www.fanpop.com/",
"username_claimed": "blue"
},
"Finanzfrage": {
"errorType": "status_code",
"url": "https://www.finanzfrage.net/nutzer/{}",
"urlMain": "https://www.finanzfrage.net/",
"username_claimed": "finanzfrage"
},
"Flickr": {
"errorType": "status_code",
"url": "https://www.flickr.com/people/{}",
"urlMain": "https://www.flickr.com/",
"username_claimed": "blue"
},
"Flightradar24": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z0-9_]{3,20}$",
"url": "https://my.flightradar24.com/{}",
"urlMain": "https://www.flightradar24.com/",
"username_claimed": "jebbrooks"
},
"Flipboard": {
"errorType": "status_code",
"regexCheck": "^([a-zA-Z0-9_]){1,15}$",
"url": "https://flipboard.com/@{}",
"urlMain": "https://flipboard.com/",
"username_claimed": "blue"
},
"Football": {
"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",
"errorType": "message",
"url": "https://www.rusfootball.info/user/{}/",
"urlMain": "https://www.rusfootball.info/",
"username_claimed": "solo87"
},
"FortniteTracker": {
"errorType": "status_code",
"url": "https://fortnitetracker.com/profile/all/{}",
"urlMain": "https://fortnitetracker.com/challenges",
"username_claimed": "blue"
},
"Forum Ophilia": {
"errorMsg": "that user does not exist",
"errorType": "message",
"isNSFW": true,
"url": "https://www.forumophilia.com/profile.php?mode=viewprofile&u={}",
"urlMain": "https://www.forumophilia.com/",
"username_claimed": "bob"
},
"Fosstodon": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z0-9_]{1,30}$",
"url": "https://fosstodon.org/@{}",
"urlMain": "https://fosstodon.org/",
"username_claimed": "blue"
},
"Framapiaf": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z0-9_]{1,30}$",
"url": "https://framapiaf.org/@{}",
"urlMain": "https://framapiaf.org",
"username_claimed": "pylapp"
},
"Freelancer": {
"errorMsg": "\"users\":{}",
"errorType": "message",
"url": "https://www.freelancer.com/u/{}",
"urlMain": "https://www.freelancer.com/",
"urlProbe": "https://www.freelancer.com/api/users/0.1/users?usernames%5B%5D={}&compact=true",
"username_claimed": "red0xff"
},
"Freesound": {
"errorType": "status_code",
"url": "https://freesound.org/people/{}/",
"urlMain": "https://freesound.org/",
"username_claimed": "blue"
},
"GNOME VCS": {
"errorType": "response_url",
"errorUrl": "https://gitlab.gnome.org/{}",
"regexCheck": "^(?!-)[a-zA-Z0-9_.-]{2,255}(? GIFs - Find & Share on GIPHY",
"url": "https://giphy.com/{}",
"urlMain": "https://giphy.com/",
"username_claimed": "red"
},
"GitBook": {
"errorType": "status_code",
"regexCheck": "^[\\w@-]+?$",
"url": "https://{}.gitbook.io/",
"urlMain": "https://gitbook.com/",
"username_claimed": "gitbook"
},
"GitHub": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$",
"url": "https://www.github.com/{}",
"urlMain": "https://www.github.com/",
"username_claimed": "blue"
},
"Warframe Market": {
"errorType": "status_code",
"request_method": "GET",
"url": "https://warframe.market/profile/{}",
"urlMain": "https://warframe.market/",
"urlProbe": "https://api.warframe.market/v2/user/{}",
"username_claimed": "kaiallalone"
},
"GitLab": {
"errorMsg": "[]",
"errorType": "message",
"url": "https://gitlab.com/{}",
"urlMain": "https://gitlab.com/",
"urlProbe": "https://gitlab.com/api/v4/users?username={}",
"username_claimed": "blue"
},
"Gitea": {
"errorType": "status_code",
"url": "https://gitea.com/{}",
"urlMain": "https://gitea.com/",
"username_claimed": "xorm"
},
"Gitee": {
"errorType": "status_code",
"url": "https://gitee.com/{}",
"urlMain": "https://gitee.com/",
"username_claimed": "wizzer"
},
"GoodReads": {
"errorType": "status_code",
"url": "https://www.goodreads.com/{}",
"urlMain": "https://www.goodreads.com/",
"username_claimed": "blue"
},
"Google Play": {
"errorMsg": "the requested URL was not found on this server",
"errorType": "message",
"url": "https://play.google.com/store/apps/developer?id={}",
"urlMain": "https://play.google.com",
"username_claimed": "GitHub"
},
"Gradle": {
"errorType": "status_code",
"regexCheck": "^(?!-)[a-zA-Z0-9-]{3,}(?User Not Found - Hive",
"errorType": "message",
"url": "https://hive.blog/@{}",
"urlMain": "https://hive.blog/",
"username_claimed": "mango-juice"
},
"Holopin": {
"errorMsg": "true",
"errorType": "message",
"request_method": "POST",
"request_payload": {
"username": "{}"
},
"url": "https://holopin.io/@{}",
"urlMain": "https://holopin.io",
"urlProbe": "https://www.holopin.io/api/auth/username",
"username_claimed": "red"
},
"Houzz": {
"errorType": "status_code",
"url": "https://houzz.com/user/{}",
"urlMain": "https://houzz.com/",
"username_claimed": "blue"
},
"HubPages": {
"errorType": "status_code",
"url": "https://hubpages.com/@{}",
"urlMain": "https://hubpages.com/",
"username_claimed": "blue"
},
"Hubski": {
"errorMsg": "No such user",
"errorType": "message",
"url": "https://hubski.com/user/{}",
"urlMain": "https://hubski.com/",
"username_claimed": "blue"
},
"HudsonRock": {
"errorMsg": "This username is not associated",
"errorType": "message",
"url": "https://cavalier.hudsonrock.com/api/json/v2/osint-tools/search-by-username?username={}",
"urlMain": "https://hudsonrock.com",
"username_claimed": "testadmin"
},
"Hugging Face": {
"errorType": "status_code",
"url": "https://huggingface.co/{}",
"urlMain": "https://huggingface.co/",
"username_claimed": "Pasanlaksitha"
},
"IFTTT": {
"errorType": "status_code",
"regexCheck": "^[A-Za-z0-9]{3,35}$",
"url": "https://www.ifttt.com/p/{}",
"urlMain": "https://www.ifttt.com/",
"username_claimed": "blue"
},
"Ifunny": {
"errorType": "status_code",
"url": "https://ifunny.co/user/{}",
"urlMain": "https://ifunny.co/",
"username_claimed": "agua"
},
"IRC-Galleria": {
"errorType": "response_url",
"errorUrl": "https://irc-galleria.net/users/search?username={}",
"url": "https://irc-galleria.net/user/{}",
"urlMain": "https://irc-galleria.net/",
"username_claimed": "appas"
},
"Icons8 Community": {
"errorType": "status_code",
"url": "https://community.icons8.com/u/{}/summary",
"urlMain": "https://community.icons8.com/",
"username_claimed": "thefourCraft"
},
"Image Fap": {
"errorMsg": "Not found",
"errorType": "message",
"isNSFW": true,
"url": "https://www.imagefap.com/profile/{}",
"urlMain": "https://www.imagefap.com/",
"username_claimed": "blue"
},
"ImgUp.cz": {
"errorType": "status_code",
"url": "https://imgup.cz/{}",
"urlMain": "https://imgup.cz/",
"username_claimed": "adam"
},
"Imgur": {
"errorType": "status_code",
"url": "https://imgur.com/user/{}",
"urlMain": "https://imgur.com/",
"urlProbe": "https://api.imgur.com/account/v1/accounts/{}?client_id=546c25a59c58ad7",
"username_claimed": "blue"
},
"imood": {
"errorType": "status_code",
"url": "https://www.imood.com/users/{}",
"urlMain": "https://www.imood.com/",
"username_claimed": "blue"
},
"Instagram": {
"errorType": "status_code",
"url": "https://instagram.com/{}",
"urlMain": "https://instagram.com/",
"urlProbe": "https://imginn.com/{}",
"username_claimed": "instagram"
},
"Instapaper": {
"errorType": "status_code",
"request_method": "GET",
"url": "https://www.instapaper.com/p/{}",
"urlMain": "https://www.instapaper.com/",
"username_claimed": "john"
},
"Instructables": {
"errorType": "status_code",
"url": "https://www.instructables.com/member/{}",
"urlMain": "https://www.instructables.com/",
"urlProbe": "https://www.instructables.com/json-api/showAuthorExists?screenName={}",
"username_claimed": "blue"
},
"Intigriti": {
"errorType": "status_code",
"regexCheck": "[a-z0-9_]{1,25}",
"request_method": "GET",
"url": "https://app.intigriti.com/profile/{}",
"urlMain": "https://app.intigriti.com",
"urlProbe": "https://api.intigriti.com/user/public/profile/{}",
"username_claimed": "blue"
},
"Ionic Forum": {
"errorType": "status_code",
"url": "https://forum.ionicframework.com/u/{}",
"urlMain": "https://forum.ionicframework.com/",
"username_claimed": "theblue222"
},
"Issuu": {
"errorType": "status_code",
"url": "https://issuu.com/{}",
"urlMain": "https://issuu.com/",
"username_claimed": "jenny"
},
"Itch.io": {
"errorType": "status_code",
"regexCheck": "^[\\w@-]+?$",
"url": "https://{}.itch.io/",
"urlMain": "https://itch.io/",
"username_claimed": "blue"
},
"Itemfix": {
"errorMsg": "ItemFix - Channel: ",
"errorType": "message",
"url": "https://www.itemfix.com/c/{}",
"urlMain": "https://www.itemfix.com/",
"username_claimed": "blue"
},
"Jellyfin Weblate": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z0-9@._-]{1,150}$",
"url": "https://translate.jellyfin.org/user/{}/",
"urlMain": "https://translate.jellyfin.org/",
"username_claimed": "EraYaN"
},
"Jimdo": {
"errorType": "status_code",
"regexCheck": "^[\\w@-]+?$",
"url": "https://{}.jimdosite.com",
"urlMain": "https://jimdosite.com/",
"username_claimed": "jenny"
},
"Joplin Forum": {
"errorType": "status_code",
"url": "https://discourse.joplinapp.org/u/{}",
"urlMain": "https://discourse.joplinapp.org/",
"username_claimed": "laurent"
},
"Jupyter Community Forum": {
"errorMsg": "Oops! That page doesn’t exist or is private.",
"errorType": "message",
"url": "https://discourse.jupyter.org/u/{}/summary",
"urlMain": "https://discourse.jupyter.org",
"username_claimed": "choldgraf"
},
"Kaggle": {
"errorType": "status_code",
"url": "https://www.kaggle.com/{}",
"urlMain": "https://www.kaggle.com/",
"username_claimed": "dansbecker"
},
"kaskus": {
"errorType": "status_code",
"url": "https://www.kaskus.co.id/@{}",
"urlMain": "https://www.kaskus.co.id",
"urlProbe": "https://www.kaskus.co.id/api/users?username={}",
"request_method": "GET",
"username_claimed": "l0mbart"
},
"Keybase": {
"errorType": "status_code",
"url": "https://keybase.io/{}",
"urlMain": "https://keybase.io/",
"username_claimed": "blue"
},
"Kick": {
"__comment__": "Cloudflare. Only viable when proxied.",
"errorType": "status_code",
"url": "https://kick.com/{}",
"urlMain": "https://kick.com/",
"urlProbe": "https://kick.com/api/v2/channels/{}",
"username_claimed": "blue"
},
"Kik": {
"errorMsg": "The page you requested was not found",
"errorType": "message",
"url": "https://kik.me/{}",
"urlMain": "http://kik.me/",
"urlProbe": "https://ws2.kik.com/user/{}",
"username_claimed": "blue"
},
"Kongregate": {
"errorType": "status_code",
"headers": {
"Accept": "text/html"
},
"regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$",
"url": "https://www.kongregate.com/accounts/{}",
"urlMain": "https://www.kongregate.com/",
"username_claimed": "blue"
},
"Kvinneguiden": {
"errorMsg": "{\"result\":\"ok\"}",
"errorType": "message",
"regexCheck": "^[a-zA-Z0-9_.-]{3,18}$",
"urlProbe": "https://forum.kvinneguiden.no/?app=core&module=system&controller=ajax&do=usernameExists&input={}",
"url": "https://forum.kvinneguiden.no",
"urlMain": "https://forum.kvinneguiden.no",
"username_claimed": "blue"
},
"LOR": {
"errorType": "status_code",
"url": "https://www.linux.org.ru/people/{}/profile",
"urlMain": "https://linux.org.ru/",
"username_claimed": "red"
},
"Laracast": {
"errorType": "status_code",
"url": "https://laracasts.com/@{}",
"urlMain": "https://laracasts.com/",
"regexCheck": "^[a-zA-Z0-9_-]{3,}$",
"username_claimed": "user1"
},
"Launchpad": {
"errorType": "status_code",
"url": "https://launchpad.net/~{}",
"urlMain": "https://launchpad.net/",
"username_claimed": "blue"
},
"LeetCode": {
"errorType": "status_code",
"url": "https://leetcode.com/{}",
"urlMain": "https://leetcode.com/",
"username_claimed": "blue"
},
"LemmyWorld": {
"errorType": "message",
"errorMsg": "Error! ",
"url": "https://lemmy.world/u/{}",
"urlMain": "https://lemmy.world",
"username_claimed": "blue"
},
"LessWrong": {
"url": "https://www.lesswrong.com/users/{}",
"urlMain": "https://www.lesswrong.com/",
"errorType": "response_url",
"errorUrl": "https://www.lesswrong.com/",
"username_claimed": "habryka"
},
"Letterboxd": {
"errorMsg": "Sorry, we can\u2019t find the page you\u2019ve requested.",
"errorType": "message",
"url": "https://letterboxd.com/{}",
"urlMain": "https://letterboxd.com/",
"username_claimed": "blue"
},
"LibraryThing": {
"errorMsg": "Error: This user doesn't exist
",
"errorType": "message",
"headers": {
"Cookie": "LTAnonSessionID=3159599315; LTUnifiedCookie=%7B%22areyouhuman%22%3A1%7D; "
},
"url": "https://www.librarything.com/profile/{}",
"urlMain": "https://www.librarything.com/",
"username_claimed": "blue"
},
"Lichess": {
"errorType": "status_code",
"url": "https://lichess.org/@/{}",
"urlMain": "https://lichess.org",
"username_claimed": "john"
},
"LinkedIn": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z0-9]{3,100}$",
"request_method": "GET",
"url": "https://linkedin.com/in/{}",
"urlMain": "https://linkedin.com",
"username_claimed": "paulpfeister"
},
"Linktree": {
"errorMsg": "\"statusCode\":404",
"errorType": "message",
"regexCheck": "^[\\w\\.]{2,30}$",
"url": "https://linktr.ee/{}",
"urlMain": "https://linktr.ee/",
"username_claimed": "anne"
},
"LinuxFR.org": {
"errorType": "status_code",
"url": "https://linuxfr.org/users/{}",
"urlMain": "https://linuxfr.org/",
"username_claimed": "pylapp"
},
"Listed": {
"errorType": "response_url",
"errorUrl": "https://listed.to/@{}",
"url": "https://listed.to/@{}",
"urlMain": "https://listed.to/",
"username_claimed": "listed"
},
"LiveJournal": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$",
"url": "https://{}.livejournal.com",
"urlMain": "https://www.livejournal.com/",
"username_claimed": "blue"
},
"Lobsters": {
"errorType": "status_code",
"regexCheck": "[A-Za-z0-9][A-Za-z0-9_-]{0,24}",
"url": "https://lobste.rs/u/{}",
"urlMain": "https://lobste.rs/",
"username_claimed": "jcs"
},
"LottieFiles": {
"errorType": "status_code",
"url": "https://lottiefiles.com/{}",
"urlMain": "https://lottiefiles.com/",
"username_claimed": "lottiefiles"
},
"LushStories": {
"errorType": "status_code",
"isNSFW": true,
"url": "https://www.lushstories.com/profile/{}",
"urlMain": "https://www.lushstories.com/",
"username_claimed": "chris_brown"
},
"MMORPG Forum": {
"errorType": "status_code",
"url": "https://forums.mmorpg.com/profile/{}",
"urlMain": "https://forums.mmorpg.com/",
"username_claimed": "goku"
},
"Mamot": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z0-9_]{1,30}$",
"url": "https://mamot.fr/@{}",
"urlMain": "https://mamot.fr/",
"username_claimed": "anciensEnssat"
},
"Medium": {
"errorMsg": "Nitro Type | Competitive Typing Game | Race Your Friends",
"errorType": "message",
"url": "https://www.nitrotype.com/racer/{}",
"urlMain": "https://www.nitrotype.com/",
"username_claimed": "jianclash"
},
"NotABug.org": {
"errorType": "status_code",
"url": "https://notabug.org/{}",
"urlMain": "https://notabug.org/",
"urlProbe": "https://notabug.org/{}/followers",
"username_claimed": "red"
},
"Nothing Community": {
"errorType": "status_code",
"url": "https://nothing.community/u/{}",
"urlMain": "https://nothing.community/",
"username_claimed": "Carl"
},
"Nyaa.si": {
"errorType": "status_code",
"url": "https://nyaa.si/user/{}",
"urlMain": "https://nyaa.si/",
"username_claimed": "blue"
},
"ObservableHQ": {
"errorType": "message",
"errorMsg": "Page not found",
"url": "https://observablehq.com/@{}",
"urlMain": "https://observablehq.com/",
"username_claimed": "mbostock"
},
"Open Collective": {
"errorType": "status_code",
"url": "https://opencollective.com/{}",
"urlMain": "https://opencollective.com/",
"username_claimed": "sindresorhus"
},
"OpenGameArt": {
"errorType": "status_code",
"url": "https://opengameart.org/users/{}",
"urlMain": "https://opengameart.org",
"username_claimed": "ski"
},
"OpenStreetMap": {
"errorType": "status_code",
"regexCheck": "^[^.]*?$",
"url": "https://www.openstreetmap.org/user/{}",
"urlMain": "https://www.openstreetmap.org/",
"username_claimed": "blue"
},
"Odysee": {
"errorMsg": " ",
"errorType": "message",
"url": "https://odysee.com/@{}",
"urlMain": "https://odysee.com/",
"username_claimed": "Odysee"
},
"Opensource": {
"errorType": "status_code",
"url": "https://opensource.com/users/{}",
"urlMain": "https://opensource.com/",
"username_claimed": "red"
},
"OurDJTalk": {
"errorMsg": "The specified member cannot be found",
"errorType": "message",
"url": "https://ourdjtalk.com/members?username={}",
"urlMain": "https://ourdjtalk.com/",
"username_claimed": "steve"
},
"Outgress": {
"errorMsg": "Outgress - Error",
"errorType": "message",
"url": "https://outgress.com/agents/{}",
"urlMain": "https://outgress.com/",
"username_claimed": "pylapp"
},
"PCGamer": {
"errorMsg": "The specified member cannot be found. Please enter a member's entire name.",
"errorType": "message",
"url": "https://forums.pcgamer.com/members/?username={}",
"urlMain": "https://pcgamer.com",
"username_claimed": "admin"
},
"PSNProfiles.com": {
"errorType": "response_url",
"errorUrl": "https://psnprofiles.com/?psnId={}",
"url": "https://psnprofiles.com/{}",
"urlMain": "https://psnprofiles.com/",
"username_claimed": "blue"
},
"Packagist": {
"errorType": "response_url",
"errorUrl": "https://packagist.org/search/?q={}&reason=vendor_not_found",
"url": "https://packagist.org/packages/{}/",
"urlMain": "https://packagist.org/",
"username_claimed": "psr"
},
"Pastebin": {
"errorMsg": "Not Found (#404)",
"errorType": "message",
"url": "https://pastebin.com/u/{}",
"urlMain": "https://pastebin.com/",
"username_claimed": "blue"
},
"Patched": {
"errorMsg": "The member you specified is either invalid or doesn't exist.",
"errorType": "message",
"url": "https://patched.sh/User/{}",
"urlMain": "https://patched.sh/",
"username_claimed": "blue"
},
"Patreon": {
"errorType": "status_code",
"url": "https://www.patreon.com/{}",
"urlMain": "https://www.patreon.com/",
"username_claimed": "blue"
},
"PentesterLab": {
"errorType": "status_code",
"regexCheck": "^[\\w]{4,30}$",
"url": "https://pentesterlab.com/profile/{}",
"urlMain": "https://pentesterlab.com/",
"username_claimed": "0day"
},
"HotUKdeals": {
"errorType": "status_code",
"url": "https://www.hotukdeals.com/profile/{}",
"urlMain": "https://www.hotukdeals.com/",
"username_claimed": "Blue",
"request_method": "GET"
},
"Mydealz": {
"errorType": "status_code",
"url": "https://www.mydealz.de/profile/{}",
"urlMain": "https://www.mydealz.de/",
"username_claimed": "blue",
"request_method": "GET"
},
"Chollometro": {
"errorType": "status_code",
"url": "https://www.chollometro.com/profile/{}",
"urlMain": "https://www.chollometro.com/",
"username_claimed": "blue",
"request_method": "GET"
},
"PepperNL": {
"errorType": "status_code",
"url": "https://nl.pepper.com/profile/{}",
"urlMain": "https://nl.pepper.com/",
"username_claimed": "Dynaw",
"request_method": "GET"
},
"PepperPL": {
"errorType": "status_code",
"url": "https://www.pepper.pl/profile/{}",
"urlMain": "https://www.pepper.pl/",
"username_claimed": "FireChicken",
"request_method": "GET"
},
"Preisjaeger": {
"errorType": "status_code",
"url": "https://www.preisjaeger.at/profile/{}",
"urlMain": "https://www.preisjaeger.at/",
"username_claimed": "Stefan",
"request_method": "GET"
},
"Pepperdeals": {
"errorType": "status_code",
"url": "https://www.pepperdeals.se/profile/{}",
"urlMain": "https://www.pepperdeals.se/",
"username_claimed": "Mark",
"request_method": "GET"
},
"PepperealsUS": {
"errorType": "status_code",
"url": "https://www.pepperdeals.com/profile/{}",
"urlMain": "https://www.pepperdeals.com/",
"username_claimed": "Stepan",
"request_method": "GET"
},
"Promodescuentos": {
"errorType": "status_code",
"url": "https://www.promodescuentos.com/profile/{}",
"urlMain": "https://www.promodescuentos.com/",
"username_claimed": "blue",
"request_method": "GET"
},
"Periscope": {
"errorType": "status_code",
"url": "https://www.periscope.tv/{}/",
"urlMain": "https://www.periscope.tv/",
"username_claimed": "blue"
},
"Pinkbike": {
"errorType": "status_code",
"regexCheck": "^[^.]*?$",
"url": "https://www.pinkbike.com/u/{}/",
"urlMain": "https://www.pinkbike.com/",
"username_claimed": "blue"
},
"pixelfed.social": {
"errorType": "status_code",
"url": "https://pixelfed.social/{}/",
"urlMain": "https://pixelfed.social",
"username_claimed": "pylapp"
},
"PlayStore": {
"errorType": "status_code",
"url": "https://play.google.com/store/apps/developer?id={}",
"urlMain": "https://play.google.com/store",
"username_claimed": "Facebook"
},
"Playstrategy": {
"errorType": "status_code",
"url": "https://playstrategy.org/@/{}",
"urlMain": "https://playstrategy.org",
"username_claimed": "oruro"
},
"Plurk": {
"errorMsg": "User Not Found!",
"errorType": "message",
"url": "https://www.plurk.com/{}",
"urlMain": "https://www.plurk.com/",
"username_claimed": "plurkoffice"
},
"PocketStars": {
"errorMsg": "Join Your Favorite Adult Stars",
"errorType": "message",
"isNSFW": true,
"url": "https://pocketstars.com/{}",
"urlMain": "https://pocketstars.com/",
"username_claimed": "hacker"
},
"Pokemon Showdown": {
"errorType": "status_code",
"url": "https://pokemonshowdown.com/users/{}",
"urlMain": "https://pokemonshowdown.com",
"username_claimed": "blue"
},
"Polarsteps": {
"errorType": "status_code",
"url": "https://polarsteps.com/{}",
"urlMain": "https://polarsteps.com/",
"urlProbe": "https://api.polarsteps.com/users/byusername/{}",
"username_claimed": "james"
},
"Polygon": {
"errorType": "status_code",
"url": "https://www.polygon.com/users/{}",
"urlMain": "https://www.polygon.com/",
"username_claimed": "swiftstickler"
},
"Polymart": {
"errorType": "response_url",
"errorUrl": "https://polymart.org/user/-1",
"url": "https://polymart.org/user/{}",
"urlMain": "https://polymart.org/",
"username_claimed": "craciu25yt"
},
"Pornhub": {
"errorType": "status_code",
"isNSFW": true,
"url": "https://pornhub.com/users/{}",
"urlMain": "https://pornhub.com/",
"username_claimed": "blue"
},
"ProductHunt": {
"errorType": "status_code",
"url": "https://www.producthunt.com/@{}",
"urlMain": "https://www.producthunt.com/",
"username_claimed": "jenny"
},
"programming.dev": {
"errorMsg": "Error!",
"errorType": "message",
"url": "https://programming.dev/u/{}",
"urlMain": "https://programming.dev",
"username_claimed": "pylapp"
},
"Pychess": {
"errorType": "message",
"errorMsg": "404",
"url": "https://www.pychess.org/@/{}",
"urlMain": "https://www.pychess.org",
"username_claimed": "gbtami"
},
"PromoDJ": {
"errorType": "status_code",
"url": "http://promodj.com/{}",
"urlMain": "http://promodj.com/",
"username_claimed": "blue"
},
"Pronouns.page": {
"errorType": "status_code",
"url": "https://pronouns.page/@{}",
"urlMain": "https://pronouns.page/",
"username_claimed": "andrea"
},
"PyPi": {
"errorType": "status_code",
"url": "https://pypi.org/user/{}",
"urlProbe": "https://pypi.org/_includes/administer-user-include/{}",
"urlMain": "https://pypi.org",
"username_claimed": "Blue"
},
"Python.org Discussions": {
"errorMsg": "Oops! That page doesn’t exist or is private.",
"errorType": "message",
"url": "https://discuss.python.org/u/{}/summary",
"urlMain": "https://discuss.python.org",
"username_claimed": "pablogsal"
},
"Rajce.net": {
"errorType": "status_code",
"regexCheck": "^[\\w@-]+?$",
"url": "https://{}.rajce.idnes.cz/",
"urlMain": "https://www.rajce.idnes.cz/",
"username_claimed": "blue"
},
"Rarible": {
"errorType": "status_code",
"url": "https://rarible.com/marketplace/api/v4/urls/{}",
"urlMain": "https://rarible.com/",
"username_claimed": "blue"
},
"Rate Your Music": {
"errorType": "status_code",
"url": "https://rateyourmusic.com/~{}",
"urlMain": "https://rateyourmusic.com/",
"username_claimed": "blue"
},
"Rclone Forum": {
"errorType": "status_code",
"url": "https://forum.rclone.org/u/{}",
"urlMain": "https://forum.rclone.org/",
"username_claimed": "ncw"
},
"RedTube": {
"errorType": "status_code",
"isNSFW": true,
"url": "https://www.redtube.com/users/{}",
"urlMain": "https://www.redtube.com/",
"username_claimed": "hacker"
},
"Redbubble": {
"errorType": "status_code",
"url": "https://www.redbubble.com/people/{}",
"urlMain": "https://www.redbubble.com/",
"username_claimed": "blue"
},
"Reddit": {
"errorMsg": "Sorry, nobody on Reddit goes by that name.",
"errorType": "message",
"headers": {
"accept-language": "en-US,en;q=0.9"
},
"url": "https://www.reddit.com/user/{}",
"urlMain": "https://www.reddit.com/",
"username_claimed": "blue"
},
"Realmeye": {
"errorMsg": "Sorry, but we either:",
"errorType": "message",
"url": "https://www.realmeye.com/player/{}",
"urlMain": "https://www.realmeye.com/",
"username_claimed": "rotmg"
},
"Reisefrage": {
"errorType": "status_code",
"url": "https://www.reisefrage.net/nutzer/{}",
"urlMain": "https://www.reisefrage.net/",
"username_claimed": "reisefrage"
},
"Replit.com": {
"errorType": "status_code",
"url": "https://replit.com/@{}",
"urlMain": "https://replit.com/",
"username_claimed": "blue"
},
"ResearchGate": {
"errorType": "response_url",
"errorUrl": "https://www.researchgate.net/directory/profiles",
"regexCheck": "\\w+_\\w+",
"url": "https://www.researchgate.net/profile/{}",
"urlMain": "https://www.researchgate.net/",
"username_claimed": "John_Smith"
},
"ReverbNation": {
"errorMsg": "Sorry, we couldn't find that page",
"errorType": "message",
"url": "https://www.reverbnation.com/{}",
"urlMain": "https://www.reverbnation.com/",
"username_claimed": "blue"
},
"Roblox": {
"errorType": "status_code",
"url": "https://www.roblox.com/user.aspx?username={}",
"urlMain": "https://www.roblox.com/",
"username_claimed": "bluewolfekiller"
},
"RocketTube": {
"errorMsg": "OOPS! Houston, we have a problem",
"errorType": "message",
"isNSFW": true,
"url": "https://www.rockettube.com/{}",
"urlMain": "https://www.rockettube.com/",
"username_claimed": "Tatteddick5600"
},
"RoyalCams": {
"errorType": "status_code",
"url": "https://royalcams.com/profile/{}",
"urlMain": "https://royalcams.com",
"username_claimed": "asuna-black"
},
"Ruby Forums": {
"errorMsg": "Oops! That page doesn’t exist or is private.",
"errorType": "message",
"url": "https://ruby-forum.com/u/{}/summary",
"urlMain": "https://ruby-forums.com",
"username_claimed": "rishard"
},
"RubyGems": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]{1,40}",
"url": "https://rubygems.org/profiles/{}",
"urlMain": "https://rubygems.org/",
"username_claimed": "blue"
},
"Rumble": {
"errorType": "status_code",
"url": "https://rumble.com/user/{}",
"urlMain": "https://rumble.com/",
"username_claimed": "John"
},
"RuneScape": {
"errorMsg": "{\"error\":\"NO_PROFILE\",\"loggedIn\":\"false\"}",
"errorType": "message",
"regexCheck": "^(?! )[\\w -]{1,12}(?Page no longer exists",
"url": "https://slideshare.net/{}",
"urlMain": "https://slideshare.net/",
"username_claimed": "blue"
},
"Slides": {
"errorCode": 204,
"errorType": "status_code",
"url": "https://slides.com/{}",
"urlMain": "https://slides.com/",
"username_claimed": "blue"
},
"SmugMug": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z]{1,35}$",
"url": "https://{}.smugmug.com",
"urlMain": "https://smugmug.com",
"username_claimed": "winchester"
},
"Smule": {
"errorMsg": "Smule | Page Not Found (404)",
"errorType": "message",
"url": "https://www.smule.com/{}",
"urlMain": "https://www.smule.com/",
"username_claimed": "blue"
},
"Snapchat": {
"errorType": "status_code",
"regexCheck": "^[a-z][a-z-_.]{3,15}",
"request_method": "GET",
"url": "https://www.snapchat.com/add/{}",
"urlMain": "https://www.snapchat.com",
"username_claimed": "teamsnapchat"
},
"SOOP": {
"errorType": "status_code",
"url": "https://www.sooplive.co.kr/station/{}",
"urlMain": "https://www.sooplive.co.kr/",
"urlProbe": "https://api-channel.sooplive.co.kr/v1.1/channel/{}/station",
"username_claimed": "udkn"
},
"SoundCloud": {
"errorType": "status_code",
"url": "https://soundcloud.com/{}",
"urlMain": "https://soundcloud.com/",
"username_claimed": "blue"
},
"SourceForge": {
"errorType": "status_code",
"url": "https://sourceforge.net/u/{}",
"urlMain": "https://sourceforge.net/",
"username_claimed": "blue"
},
"SoylentNews": {
"errorMsg": "The user you requested does not exist, no matter how much you wish this might be the case.",
"errorType": "message",
"url": "https://soylentnews.org/~{}",
"urlMain": "https://soylentnews.org",
"username_claimed": "adam"
},
"SpeakerDeck": {
"errorType": "status_code",
"url": "https://speakerdeck.com/{}",
"urlMain": "https://speakerdeck.com/",
"username_claimed": "pylapp"
},
"Speedrun.com": {
"errorType": "status_code",
"url": "https://speedrun.com/users/{}",
"urlMain": "https://speedrun.com/",
"username_claimed": "example"
},
"Spells8": {
"errorType": "status_code",
"url": "https://forum.spells8.com/u/{}",
"urlMain": "https://spells8.com",
"username_claimed": "susurrus"
},
"Splice": {
"errorType": "status_code",
"url": "https://splice.com/{}",
"urlMain": "https://splice.com/",
"username_claimed": "splice"
},
"Splits.io": {
"errorType": "status_code",
"regexCheck": "^[^.]*?$",
"url": "https://splits.io/users/{}",
"urlMain": "https://splits.io",
"username_claimed": "cambosteve"
},
"Sporcle": {
"errorType": "status_code",
"url": "https://www.sporcle.com/user/{}/people",
"urlMain": "https://www.sporcle.com/",
"username_claimed": "blue"
},
"Sportlerfrage": {
"errorType": "status_code",
"url": "https://www.sportlerfrage.net/nutzer/{}",
"urlMain": "https://www.sportlerfrage.net/",
"username_claimed": "sportlerfrage"
},
"SportsRU": {
"errorType": "status_code",
"url": "https://www.sports.ru/profile/{}/",
"urlMain": "https://www.sports.ru/",
"username_claimed": "blue"
},
"Spotify": {
"errorType": "status_code",
"url": "https://open.spotify.com/user/{}",
"urlMain": "https://open.spotify.com/",
"username_claimed": "blue"
},
"Star Citizen": {
"errorMsg": "404",
"errorType": "message",
"url": "https://robertsspaceindustries.com/citizens/{}",
"urlMain": "https://robertsspaceindustries.com/",
"username_claimed": "blue"
},
"Status Cafe": {
"errorMsg": "Page Not Found",
"errorType": "message",
"url": "https://status.cafe/users/{}",
"urlMain": "https://status.cafe/",
"username_claimed": "blue"
},
"Steam Community (Group)": {
"errorMsg": "No group could be retrieved for the given URL",
"errorType": "message",
"url": "https://steamcommunity.com/groups/{}",
"urlMain": "https://steamcommunity.com/",
"username_claimed": "blue"
},
"Steam Community (User)": {
"errorMsg": "The specified profile could not be found",
"errorType": "message",
"url": "https://steamcommunity.com/id/{}/",
"urlMain": "https://steamcommunity.com/",
"username_claimed": "blue"
},
"Strava": {
"errorType": "status_code",
"regexCheck": "^[^.]*?$",
"url": "https://www.strava.com/athletes/{}",
"urlMain": "https://www.strava.com/",
"username_claimed": "blue"
},
"SublimeForum": {
"errorType": "status_code",
"url": "https://forum.sublimetext.com/u/{}",
"urlMain": "https://forum.sublimetext.com/",
"username_claimed": "blue"
},
"TETR.IO": {
"errorMsg": "No such user!",
"errorType": "message",
"url": "https://ch.tetr.io/u/{}",
"urlMain": "https://tetr.io",
"urlProbe": "https://ch.tetr.io/api/users/{}",
"username_claimed": "osk"
},
"TheMovieDB": {
"errorType": "status_code",
"url": "https://www.themoviedb.org/u/{}",
"urlMain": "https://www.themoviedb.org/",
"username_claimed": "blue"
},
"TikTok": {
"url": "https://www.tiktok.com/@{}",
"urlMain": "https://www.tiktok.com",
"errorType": "message",
"errorMsg": [
"\"statusCode\":10221",
"Govt. of India decided to block 59 apps"
],
"username_claimed": "charlidamelio"
},
"Tiendanube": {
"url": "https://{}.mitiendanube.com/",
"urlMain": "https://www.tiendanube.com/",
"errorType": "status_code",
"username_claimed": "blue"
},
"Topcoder": {
"errorType": "status_code",
"url": "https://profiles.topcoder.com/{}/",
"urlMain": "https://topcoder.com/",
"username_claimed": "USER",
"urlProbe": "https://api.topcoder.com/v5/members/{}",
"regexCheck": "^[a-zA-Z0-9_.]+$"
},
"Topmate": {
"errorType": "status_code",
"url": "https://topmate.io/{}",
"urlMain": "https://topmate.io/",
"username_claimed": "blue"
},
"TRAKTRAIN": {
"errorType": "status_code",
"url": "https://traktrain.com/{}",
"urlMain": "https://traktrain.com/",
"username_claimed": "traktrain"
},
"Telegram": {
"errorMsg": [
"Telegram Messenger ",
"If you have Telegram , you can contact User ",
"429 Too Many Requests "
],
"errorType": "message",
"regexCheck": "^[a-zA-Z0-9_]{1,15}$",
"url": "https://x.com/{}",
"urlMain": "https://x.com/",
"urlProbe": "https://nitter.privacydev.net/{}",
"username_claimed": "blue"
},
"Typeracer": {
"errorMsg": "Profile Not Found",
"errorType": "message",
"url": "https://data.typeracer.com/pit/profile?user={}",
"urlMain": "https://typeracer.com",
"username_claimed": "blue"
},
"Ultimate-Guitar": {
"errorType": "status_code",
"url": "https://ultimate-guitar.com/u/{}",
"urlMain": "https://ultimate-guitar.com/",
"username_claimed": "blue"
},
"Unsplash": {
"errorType": "status_code",
"regexCheck": "^[a-z0-9_]{1,60}$",
"url": "https://unsplash.com/@{}",
"urlMain": "https://unsplash.com/",
"username_claimed": "jenny"
},
"Untappd": {
"errorType": "status_code",
"url": "https://untappd.com/user/{}",
"urlMain": "https://untappd.com/",
"username_claimed": "untappd"
},
"Valorant Forums": {
"errorMsg": "The page you requested could not be found.",
"errorType": "message",
"url": "https://valorantforums.com/u/{}",
"urlMain": "https://valorantforums.com",
"username_claimed": "Wolves"
},
"VK": {
"errorType": "response_url",
"errorUrl": "https://www.quora.com/profile/{}",
"url": "https://vk.com/{}",
"urlMain": "https://vk.com/",
"username_claimed": "brown"
},
"VSCO": {
"errorType": "status_code",
"url": "https://vsco.co/{}",
"urlMain": "https://vsco.co/",
"username_claimed": "blue"
},
"Velog": {
"errorType": "status_code",
"url": "https://velog.io/@{}/posts",
"urlMain": "https://velog.io/",
"username_claimed": "qlgks1"
},
"Velomania": {
"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.",
"errorType": "message",
"url": "https://forum.velomania.ru/member.php?username={}",
"urlMain": "https://forum.velomania.ru/",
"username_claimed": "red"
},
"Venmo": {
"errorMsg": ["Venmo | Page Not Found"],
"errorType": "message",
"headers": {
"Host": "account.venmo.com"
},
"url": "https://account.venmo.com/u/{}",
"urlMain": "https://venmo.com/",
"urlProbe": "https://test1.venmo.com/u/{}",
"username_claimed": "jenny"
},
"Vero": {
"errorMsg": "Not Found",
"errorType": "message",
"request_method": "GET",
"url": "https://vero.co/{}",
"urlMain": "https://vero.co/",
"username_claimed": "blue"
},
"Vimeo": {
"errorType": "status_code",
"url": "https://vimeo.com/{}",
"urlMain": "https://vimeo.com/",
"username_claimed": "blue"
},
"VirusTotal": {
"errorType": "status_code",
"request_method": "GET",
"url": "https://www.virustotal.com/gui/user/{}",
"urlMain": "https://www.virustotal.com/",
"urlProbe": "https://www.virustotal.com/ui/users/{}/avatar",
"username_claimed": "blue"
},
"VLR": {
"errorType": "status_code",
"url": "https://www.vlr.gg/user/{}",
"urlMain": "https://www.vlr.gg",
"username_claimed": "optms"
},
"WICG Forum": {
"errorType": "status_code",
"regexCheck": "^(?![.-])[a-zA-Z0-9_.-]{3,20}$",
"url": "https://discourse.wicg.io/u/{}/summary",
"urlMain": "https://discourse.wicg.io/",
"username_claimed": "stefano"
},
"Wakatime": {
"errorType": "status_code",
"url": "https://wakatime.com/@{}",
"urlMain": "https://wakatime.com/",
"username_claimed": "blue"
},
"Warrior Forum": {
"errorType": "status_code",
"url": "https://www.warriorforum.com/members/{}.html",
"urlMain": "https://www.warriorforum.com/",
"username_claimed": "blue"
},
"Wattpad": {
"errorType": "status_code",
"url": "https://www.wattpad.com/user/{}",
"urlMain": "https://www.wattpad.com/",
"urlProbe": "https://www.wattpad.com/api/v3/users/{}/",
"username_claimed": "Dogstho7951"
},
"WebNode": {
"errorType": "status_code",
"regexCheck": "^[\\w@-]+?$",
"url": "https://{}.webnode.cz/",
"urlMain": "https://www.webnode.cz/",
"username_claimed": "radkabalcarova"
},
"Weblate": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z0-9@._-]{1,150}$",
"url": "https://hosted.weblate.org/user/{}/",
"urlMain": "https://hosted.weblate.org/",
"username_claimed": "adam"
},
"Weebly": {
"errorType": "status_code",
"regexCheck": "^[a-zA-Z0-9-]{1,63}$",
"url": "https://{}.weebly.com/",
"urlMain": "https://weebly.com/",
"username_claimed": "blue"
},
"Wikidot": {
"errorMsg": "User does not exist.",
"errorType": "message",
"url": "http://www.wikidot.com/user:info/{}",
"urlMain": "http://www.wikidot.com/",
"username_claimed": "blue"
},
"Wikipedia": {
"errorMsg": "centralauth-admin-nonexistent:",
"errorType": "message",
"url": "https://en.wikipedia.org/wiki/Special:CentralAuth/{}?uselang=qqx",
"urlMain": "https://www.wikipedia.org/",
"username_claimed": "Hoadlck"
},
"Windy": {
"errorType": "status_code",
"url": "https://community.windy.com/user/{}",
"urlMain": "https://windy.com/",
"username_claimed": "blue"
},
"Wix": {
"errorType": "status_code",
"regexCheck": "^[\\w@-]+?$",
"url": "https://{}.wix.com",
"urlMain": "https://wix.com/",
"username_claimed": "support"
},
"WolframalphaForum": {
"errorType": "status_code",
"url": "https://community.wolfram.com/web/{}/home",
"urlMain": "https://community.wolfram.com/",
"username_claimed": "unico"
},
"WordPress": {
"errorType": "response_url",
"errorUrl": "wordpress.com/typo/?subdomain=",
"regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$",
"url": "https://{}.wordpress.com/",
"urlMain": "https://wordpress.com",
"username_claimed": "blue"
},
"WordPressOrg": {
"errorType": "response_url",
"errorUrl": "https://wordpress.org",
"url": "https://profiles.wordpress.org/{}/",
"urlMain": "https://wordpress.org/",
"username_claimed": "blue"
},
"Wordnik": {
"errorMsg": "Page Not Found",
"errorType": "message",
"regexCheck": "^[a-zA-Z0-9_.+-]{1,40}$",
"url": "https://www.wordnik.com/users/{}",
"urlMain": "https://www.wordnik.com/",
"username_claimed": "blue"
},
"Wykop": {
"errorType": "status_code",
"url": "https://www.wykop.pl/ludzie/{}",
"urlMain": "https://www.wykop.pl",
"username_claimed": "blue"
},
"Xbox Gamertag": {
"errorType": "status_code",
"url": "https://xboxgamertag.com/search/{}",
"urlMain": "https://xboxgamertag.com/",
"username_claimed": "red"
},
"Xvideos": {
"errorType": "status_code",
"isNSFW": true,
"url": "https://xvideos.com/profiles/{}",
"urlMain": "https://xvideos.com/",
"username_claimed": "blue"
},
"YandexMusic": {
"__comment__": "The first and third errorMsg relate to geo-restrictions and bot detection/captchas.",
"errorMsg": [
"\u041e\u0448\u0438\u0431\u043a\u0430 404",
" Threads • Log in",
"errorType": "message",
"headers": {
"Sec-Fetch-Mode": "navigate"
},
"url": "https://www.threads.net/@{}",
"urlMain": "https://www.threads.net/",
"username_claimed": "zuck"
},
"toster": {
"errorType": "status_code",
"url": "https://www.toster.ru/user/{}/answers",
"urlMain": "https://www.toster.ru/",
"username_claimed": "adam"
},
"tumblr": {
"errorType": "status_code",
"url": "https://{}.tumblr.com/",
"urlMain": "https://www.tumblr.com/",
"username_claimed": "goku"
},
"uid": {
"errorType": "status_code",
"url": "http://uid.me/{}",
"urlMain": "https://uid.me/",
"username_claimed": "blue"
},
"write.as": {
"errorType": "status_code",
"url": "https://write.as/{}",
"urlMain": "https://write.as",
"username_claimed": "pylapp"
},
"xHamster": {
"errorType": "status_code",
"isNSFW": true,
"url": "https://xhamster.com/users/{}",
"urlMain": "https://xhamster.com",
"urlProbe": "https://xhamster.com/users/{}?old_browser=true",
"username_claimed": "blue"
},
"znanylekarz.pl": {
"errorType": "status_code",
"url": "https://www.znanylekarz.pl/{}",
"urlMain": "https://znanylekarz.pl",
"username_claimed": "janusz-nowak"
},
"Platzi": {
"errorType": "status_code",
"errorCode": 404,
"url": "https://platzi.com/p/{}/",
"urlMain": "https://platzi.com/",
"username_claimed": "freddier",
"request_method": "GET"
},
"BabyRu": {
"url": "https://www.baby.ru/u/{}",
"urlMain": "https://www.baby.ru/",
"errorType": "message",
"errorMsg": [
"\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",
"\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"
],
"username_claimed": "example"
}
}
================================================
FILE: the_big_brother/resources/data.schema.json
================================================
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Sherlock Target Manifest",
"description": "Social media targets to probe for the existence of known usernames",
"type": "object",
"properties": {
"$schema": { "type": "string" }
},
"patternProperties": {
"^(?!\\$).*?$": {
"type": "object",
"description": "Target name and associated information (key should be human readable name)",
"required": ["url", "urlMain", "errorType", "username_claimed"],
"properties": {
"url": { "type": "string" },
"urlMain": { "type": "string" },
"urlProbe": { "type": "string" },
"username_claimed": { "type": "string" },
"regexCheck": { "type": "string" },
"isNSFW": { "type": "boolean" },
"headers": { "type": "object" },
"request_payload": { "type": "object" },
"__comment__": {
"type": "string",
"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."
},
"tags": {
"oneOf": [
{ "$ref": "#/$defs/tag" },
{ "type": "array", "items": { "$ref": "#/$defs/tag" } }
]
},
"request_method": {
"type": "string",
"enum": ["GET", "POST", "HEAD", "PUT"]
},
"errorType": {
"oneOf": [
{
"type": "string",
"enum": ["message", "response_url", "status_code"]
},
{
"type": "array",
"items": {
"type": "string",
"enum": ["message", "response_url", "status_code"]
}
}
]
},
"errorMsg": {
"oneOf": [
{ "type": "string" },
{ "type": "array", "items": { "type": "string" } }
]
},
"errorCode": {
"oneOf": [
{ "type": "integer" },
{ "type": "array", "items": { "type": "integer" } }
]
},
"errorUrl": { "type": "string" },
"response_url": { "type": "string" }
},
"dependencies": {
"errorMsg": {
"oneOf": [
{ "properties": { "errorType": { "const": "message" } } },
{
"properties": {
"errorType": {
"type": "array",
"contains": { "const": "message" }
}
}
}
]
},
"errorUrl": {
"oneOf": [
{ "properties": { "errorType": { "const": "response_url" } } },
{
"properties": {
"errorType": {
"type": "array",
"contains": { "const": "response_url" }
}
}
}
]
},
"errorCode": {
"oneOf": [
{ "properties": { "errorType": { "const": "status_code" } } },
{
"properties": {
"errorType": {
"type": "array",
"contains": { "const": "status_code" }
}
}
}
]
}
},
"allOf": [
{
"if": {
"anyOf": [
{ "properties": { "errorType": { "const": "message" } } },
{
"properties": {
"errorType": {
"type": "array",
"contains": { "const": "message" }
}
}
}
]
},
"then": { "required": ["errorMsg"] }
},
{
"if": {
"anyOf": [
{ "properties": { "errorType": { "const": "response_url" } } },
{
"properties": {
"errorType": {
"type": "array",
"contains": { "const": "response_url" }
}
}
}
]
},
"then": { "required": ["errorUrl"] }
}
],
"additionalProperties": false
}
},
"additionalProperties": false,
"$defs": {
"tag": { "type": "string", "enum": ["adult", "gaming"] }
}
}
================================================
FILE: the_big_brother/result.py
================================================
"""The Big Brother Result Module
This module defines various objects for recording the results of queries.
"""
from enum import Enum
class QueryStatus(Enum):
"""Query Status Enumeration.
Describes status of query about a given username.
"""
CLAIMED = "Claimed" # Username Detected
AVAILABLE = "Available" # Username Not Detected
UNKNOWN = "Unknown" # Error Occurred While Trying To Detect Username
ILLEGAL = "Illegal" # Username Not Allowable For This Site
WAF = "WAF" # Request blocked by WAF (i.e. Cloudflare)
def __str__(self):
"""Convert Object To String.
Keyword Arguments:
self -- This object.
Return Value:
Nicely formatted string to get information about this object.
"""
return self.value
class QueryResult():
"""Query Result Object.
Describes result of query about a given username.
"""
def __init__(self, username, site_name, site_url_user, status,
query_time=None, context=None):
"""Create Query Result Object.
Contains information about a specific method of detecting usernames on
a given type of web sites.
Keyword Arguments:
self -- This object.
username -- String indicating username that query result
was about.
site_name -- String which identifies site.
site_url_user -- String containing URL for username on site.
NOTE: The site may or may not exist: this
just indicates what the name would
be, if it existed.
status -- Enumeration of type QueryStatus() indicating
the status of the query.
query_time -- Time (in seconds) required to perform query.
Default of None.
context -- String indicating any additional context
about the query. For example, if there was
an error, this might indicate the type of
error that occurred.
Default of None.
Return Value:
Nothing.
"""
self.username = username
self.site_name = site_name
self.site_url_user = site_url_user
self.status = status
self.query_time = query_time
self.context = context
return
def __str__(self):
"""Convert Object To String.
Keyword Arguments:
self -- This object.
Return Value:
Nicely formatted string to get information about this object.
"""
status = str(self.status)
if self.context is not None:
# There is extra context information available about the results.
# Append it to the normal response text.
status += f" ({self.context})"
return status
================================================
FILE: the_big_brother/reverse_search.py
================================================
from playwright.async_api import async_playwright
import urllib.parse
import asyncio
import random
class ReverseImageSearcher:
def __init__(self, headless=True):
self.headless = headless
async def _search_google(self, context, encoded_url):
results = []
try:
print(" [+] Scanning Google Images... (Async)")
page = await context.new_page()
# Using Google Images instead of Lens, with SafeSearch OFF
url = f"https://www.google.com/searchbyimage?image_url={encoded_url}&safe=off"
await page.goto(url, timeout=30000)
# CONSENT HANDLING
try:
# Try generic "Reject all" or "Accept all" buttons which cover most EU/US cases
if await page.get_by_role("button", name="Reject all").is_visible():
await page.get_by_role("button", name="Reject all").click()
elif await page.get_by_role("button", name="Accept all").is_visible():
await page.get_by_role("button", name="Accept all").click()
except: pass
await asyncio.sleep(random.uniform(2.5, 4.0))
results = await page.evaluate("""() => {
const imgs = Array.from(document.querySelectorAll('img'));
return imgs
.map(img => img.src || img.getAttribute('data-src'))
.filter(src => src && src.startsWith('http') && src.length > 80 && !src.includes('gstatic') && !src.includes('google'))
.slice(0, 5);
}""")
print(f" [+] Google: Found {len(results)} matches.")
await page.close()
except Exception as e:
print(f" [-] Google Error: {e}")
return results
async def _search_bing(self, context, encoded_url):
results = []
try:
print(" [+] Scanning Bing Visual... (Async)")
page = await context.new_page()
url = f"https://www.bing.com/images/search?view=detailv2&iss=sbi&form=SBIHMP&q=imgurl:{encoded_url}&adlt=off"
await page.goto(url, timeout=30000)
# Cookie banner check
try:
if await page.locator('#bnp_btn_reject').is_visible():
await page.click('#bnp_btn_reject')
except: pass
await asyncio.sleep(random.uniform(2.0, 3.5))
results = await page.evaluate("""() => {
const imgs = Array.from(document.querySelectorAll('img'));
return imgs
.map(img => img.src || img.getAttribute('data-src'))
.filter(src => src && src.startsWith('http') && src.length > 50 && !src.includes('bing.com'))
.slice(0, 5);
}""")
print(f" [+] Bing: Found {len(results)} matches.")
await page.close()
except Exception as e:
print(f" [-] Bing Error: {e}")
return results
async def _search_yandex(self, context, encoded_url):
results = []
try:
print(" [+] Scanning Yandex Visual... (Async)")
page = await context.new_page()
url = f"https://yandex.com/images/search?rpt=imageview&url={encoded_url}"
# Yandex needs retry logic often
try:
await page.goto(url, timeout=40000)
except:
await page.reload()
await asyncio.sleep(random.uniform(3.0, 5.0))
results = await page.evaluate("""() => {
const imgs = Array.from(document.querySelectorAll('.serp-item__thumb, img.serp-item__img, .CbirSites-ItemThumb'));
return imgs
.map(img => img.src || img.getAttribute('data-src'))
.filter(src => src && src.startsWith('http') && src.length > 50)
.slice(0, 5);
}""")
print(f" [+] Yandex: Found {len(results)} matches.")
await page.close()
except Exception as e:
print(f" [-] Yandex Error: {e}")
return results
async def _search_tineye(self, context, encoded_url):
results = []
try:
print(" [+] Scanning TinEye... (Async)")
page = await context.new_page()
url = f"https://tineye.com/search?url={encoded_url}"
await page.goto(url, timeout=30000)
await asyncio.sleep(random.uniform(2.0, 4.0))
results = await page.evaluate("""() => {
// TinEye results are usually in .match div with .match-thumb img
const imgs = Array.from(document.querySelectorAll('.match-thumb img, .result-match img'));
return imgs
.map(img => img.src)
.filter(src => src && src.startsWith('http'))
.slice(0, 5);
}""")
print(f" [+] TinEye: Found {len(results)} matches.")
await page.close()
except Exception as e:
print(f" [-] TinEye Error: {e}")
return results
async def search(self, image_url: str) -> dict:
results = {"google": [], "bing": [], "yandex": [], "tineye": []}
encoded_url = urllib.parse.quote(image_url)
print(f"[*] Starting Async Quad-Vector Search for: {image_url}")
async with async_playwright() as p:
browser = await p.chromium.launch(
headless=self.headless,
args=['--disable-blink-features=AutomationControlled']
)
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")
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")
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")
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")
# Run in parallel
g_res, b_res, y_res, t_res = await asyncio.gather(
self._search_google(ctx_g, encoded_url),
self._search_bing(ctx_b, encoded_url),
self._search_yandex(ctx_y, encoded_url),
self._search_tineye(ctx_t, encoded_url)
)
results["google"] = g_res
results["bing"] = b_res
results["yandex"] = y_res
results["tineye"] = t_res
await browser.close()
print("[*] Deep Search Complete.")
return results
================================================
FILE: the_big_brother/scanner.py
================================================
#! /usr/bin/env python3
"""
Find Usernames Across Social Networks Module
This module contains the main logic to search for usernames at social
networks.
"""
import sys
try:
from the_big_brother.__init__ import import_error_test_var # noqa: F401
except ImportError:
print("Did you run The Big Brother with `python3 the_big_brother/scanner.py ...`?")
print("This is an outdated method. Please use the installed package or run as module.")
sys.exit(1)
import csv
import signal
import pandas as pd
import os
import re
from argparse import ArgumentParser, RawDescriptionHelpFormatter
from json import loads as json_loads
from time import monotonic, sleep
from typing import Optional, Union
import requests
from requests_futures.sessions import FuturesSession
from the_big_brother.__init__ import (
__longname__,
__shortname__,
__version__,
forge_api_latest_release,
)
from the_big_brother.result import QueryStatus
from the_big_brother.result import QueryResult
from the_big_brother.notify import QueryNotify
from the_big_brother.notify import QueryNotifyPrint
from the_big_brother.sites import SitesInformation
from colorama import init
from argparse import ArgumentTypeError
class BigBrotherFuturesSession(FuturesSession):
def request(self, method, url, hooks=None, *args, **kwargs):
"""Request URL.
This extends the FuturesSession request method to calculate a response
time metric to each request.
It is taken (almost) directly from the following Stack Overflow answer:
https://github.com/ross/requests-futures#working-in-the-background
Keyword Arguments:
self -- This object.
method -- String containing method desired for request.
url -- String containing URL for request.
hooks -- Dictionary containing hooks to execute after
request finishes.
args -- Arguments.
kwargs -- Keyword arguments.
Return Value:
Request object.
"""
# Record the start time for the request.
if hooks is None:
hooks = {}
start = monotonic()
def response_time(resp, *args, **kwargs):
"""Response Time Hook.
Keyword Arguments:
resp -- Response object.
args -- Arguments.
kwargs -- Keyword arguments.
Return Value:
Nothing.
"""
resp.elapsed = monotonic() - start
return
# Install hook to execute when response completes.
# Make sure that the time measurement hook is first, so we will not
# track any later hook's execution time.
try:
if isinstance(hooks["response"], list):
hooks["response"].insert(0, response_time)
elif isinstance(hooks["response"], tuple):
# Convert tuple to list and insert time measurement hook first.
hooks["response"] = list(hooks["response"])
hooks["response"].insert(0, response_time)
else:
# Must have previously contained a single hook function,
# so convert to list.
hooks["response"] = [response_time, hooks["response"]]
except KeyError:
# No response hook was already defined, so install it ourselves.
hooks["response"] = [response_time]
return super(BigBrotherFuturesSession, self).request(
method, url, hooks=hooks, *args, **kwargs
)
def get_response(request_future, error_type, social_network):
# Default for Response object if some failure occurs.
response = None
error_context = "General Unknown Error"
exception_text = None
try:
response = request_future.result()
if response.status_code:
# Status code exists in response object
error_context = None
except requests.exceptions.HTTPError as errh:
error_context = "HTTP Error"
exception_text = str(errh)
except requests.exceptions.ProxyError as errp:
error_context = "Proxy Error"
exception_text = str(errp)
except requests.exceptions.ConnectionError as errc:
error_context = "Error Connecting"
exception_text = str(errc)
except requests.exceptions.Timeout as errt:
error_context = "Timeout Error"
exception_text = str(errt)
except requests.exceptions.RequestException as err:
error_context = "Unknown Error"
exception_text = str(err)
return response, error_context, exception_text
def interpolate_string(input_object, username):
if isinstance(input_object, str):
return input_object.replace("{}", username)
elif isinstance(input_object, dict):
return {k: interpolate_string(v, username) for k, v in input_object.items()}
elif isinstance(input_object, list):
return [interpolate_string(i, username) for i in input_object]
return input_object
def check_for_parameter(username):
"""checks if {?} exists in the username
if exist it means that scanner is looking for more multiple username"""
return "{?}" in username
checksymbols = ["_", "-", "."]
def multiple_usernames(username):
"""replace the parameter with with symbols and return a list of usernames"""
allUsernames = []
for i in checksymbols:
allUsernames.append(username.replace("{?}", i))
return allUsernames
def scan(
username: str,
site_data: dict[str, dict[str, str]],
query_notify: QueryNotify,
dump_response: bool = False,
proxy: Optional[str] = None,
timeout: int = 60,
) -> dict[str, dict[str, Union[str, QueryResult]]]:
"""Run The Big Brother Analysis.
Checks for existence of username on various social media sites.
Keyword Arguments:
username -- String indicating username that report
should be created against.
site_data -- Dictionary containing all of the site data.
query_notify -- Object with base type of QueryNotify().
This will be used to notify the caller about
query results.
proxy -- String indicating the proxy URL
timeout -- Time in seconds to wait before timing out request.
Default is 60 seconds.
Return Value:
Dictionary containing results from report. Key of dictionary is the name
of the social network site, and the value is another dictionary with
the following keys:
url_main: URL of main site.
url_user: URL of user on site (if account exists).
status: QueryResult() object indicating results of test for
account existence.
http_status: HTTP status code of query which checked for existence on
site.
response_text: Text that came back from request. May be None if
there was an HTTP error when checking for existence.
"""
# Notify caller that we are starting the query.
query_notify.start(username)
# Normal requests
underlying_session = requests.session()
# Limit number of workers to 20.
# This is probably vastly overkill.
if len(site_data) >= 20:
max_workers = 20
else:
max_workers = len(site_data)
# Create multi-threaded session for all requests.
session = BigBrotherFuturesSession(
max_workers=max_workers, session=underlying_session
)
# Results from analysis of all sites
results_total = {}
# First create futures for all requests. This allows for the requests to run in parallel
for social_network, net_info in site_data.items():
# Results from analysis of this specific site
results_site = {"url_main": net_info.get("urlMain")}
# Record URL of main site
# A user agent is needed because some sites don't return the correct
# information since they think that we are bots (Which we actually are...)
headers = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:129.0) Gecko/20100101 Firefox/129.0",
}
if "headers" in net_info:
# Override/append any extra headers required by a given site.
headers.update(net_info["headers"])
# URL of user on site (if it exists)
url = interpolate_string(net_info["url"], username.replace(' ', '%20'))
# Don't make request if username is invalid for the site
regex_check = net_info.get("regexCheck")
if regex_check and re.search(regex_check, username) is None:
# No need to do the check at the site: this username is not allowed.
results_site["status"] = QueryResult(
username, social_network, url, QueryStatus.ILLEGAL
)
results_site["url_user"] = ""
results_site["http_status"] = ""
results_site["response_text"] = ""
query_notify.update(results_site["status"])
else:
# URL of user on site (if it exists)
results_site["url_user"] = url
url_probe = net_info.get("urlProbe")
request_method = net_info.get("request_method")
request_payload = net_info.get("request_payload")
request = None
if request_method is not None:
if request_method == "GET":
request = session.get
elif request_method == "HEAD":
request = session.head
elif request_method == "POST":
request = session.post
elif request_method == "PUT":
request = session.put
else:
raise RuntimeError(f"Unsupported request_method for {url}")
if request_payload is not None:
request_payload = interpolate_string(request_payload, username)
if url_probe is None:
# Probe URL is normal one seen by people out on the web.
url_probe = url
else:
# There is a special URL for probing existence separate
# from where the user profile normally can be found.
url_probe = interpolate_string(url_probe, username)
if request is None:
if net_info["errorType"] == "status_code":
# In most cases when we are detecting by status code,
# it is not necessary to get the entire body: we can
# detect fine with just the HEAD response.
request = session.head
else:
# Either this detect method needs the content associated
# with the GET response, or this specific website will
# not respond properly unless we request the whole page.
request = session.get
# Add delay to prevent rate limiting (reduced for speed)
sleep(0.1)
if net_info["errorType"] == "response_url":
# Site forwards request to a different URL if username not
# found. Disallow the redirect so we can capture the
# http status from the original URL request.
allow_redirects = False
else:
# Allow whatever redirect that the site wants to do.
# The final result of the request will be what is available.
allow_redirects = True
# This future starts running the request in a new thread, doesn't block the main thread
if proxy is not None:
proxies = {"http": proxy, "https": proxy}
future = request(
url=url_probe,
headers=headers,
proxies=proxies,
allow_redirects=allow_redirects,
timeout=timeout,
json=request_payload,
)
else:
future = request(
url=url_probe,
headers=headers,
allow_redirects=allow_redirects,
timeout=timeout,
json=request_payload,
)
# Store future in data for access later
net_info["request_future"] = future
# Add this site's results into final dictionary with all the other results.
results_total[social_network] = results_site
# Open the file containing account links
for social_network, net_info in site_data.items():
# Retrieve results again
results_site = results_total.get(social_network)
# Retrieve other site information again
url = results_site.get("url_user")
status = results_site.get("status")
if status is not None:
# We have already determined the user doesn't exist here
continue
# Get the expected error type
error_type = net_info["errorType"]
if isinstance(error_type, str):
error_type: list[str] = [error_type]
# Retrieve future and ensure it has finished
future = net_info["request_future"]
r, error_text, exception_text = get_response(
request_future=future, error_type=error_type, social_network=social_network
)
# Get response time for response of our request.
try:
response_time = r.elapsed
except AttributeError:
response_time = None
# Attempt to get request information
try:
http_status = r.status_code
except Exception:
http_status = "?"
try:
response_text = r.text.encode(r.encoding or "UTF-8")
except Exception:
response_text = ""
query_status = QueryStatus.UNKNOWN
error_context = None
# As WAFs advance and evolve, they will occasionally block The Big Brother and
# lead to false positives and negatives. Fingerprints should be added
# here to filter results that fail to bypass WAFs. Fingerprints should
# be highly targetted. Comment at the end of each fingerprint to
# indicate target and date fingerprinted.
WAFHitMsgs = [
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
r'', # 2024-11-11 Cloudflare error page
r'AwsWafIntegration.forceRefreshToken', # 2024-11-11 Cloudfront (AWS)
r'{return l.onPageView}}),Object.defineProperty(r,"perimeterxIdentifiers",{enumerable:' # 2024-04-09 PerimeterX / Human Security
]
if error_text is not None:
error_context = error_text
elif any(hitMsg in r.text for hitMsg in WAFHitMsgs):
query_status = QueryStatus.WAF
else:
if any(errtype not in ["message", "status_code", "response_url"] for errtype in error_type):
error_context = f"Unknown error type '{error_type}' for {social_network}"
query_status = QueryStatus.UNKNOWN
else:
if "message" in error_type:
# error_flag True denotes no error found in the HTML
# error_flag False denotes error found in the HTML
error_flag = True
errors = net_info.get("errorMsg")
# errors will hold the error message
# it can be string or list
# by isinstance method we can detect that
# and handle the case for strings as normal procedure
# and if its list we can iterate the errors
if isinstance(errors, str):
# Checks if the error message is in the HTML
# if error is present we will set flag to False
if errors in r.text:
error_flag = False
else:
# If it's list, it will iterate all the error message
for error in errors:
if error in r.text:
error_flag = False
break
if error_flag:
query_status = QueryStatus.CLAIMED
else:
query_status = QueryStatus.AVAILABLE
if "status_code" in error_type and query_status is not QueryStatus.AVAILABLE:
error_codes = net_info.get("errorCode")
query_status = QueryStatus.CLAIMED
# Type consistency, allowing for both singlets and lists in manifest
if isinstance(error_codes, int):
error_codes = [error_codes]
if error_codes is not None and r.status_code in error_codes:
query_status = QueryStatus.AVAILABLE
elif r.status_code >= 300 or r.status_code < 200:
query_status = QueryStatus.AVAILABLE
if "response_url" in error_type and query_status is not QueryStatus.AVAILABLE:
# For this detection method, we have turned off the redirect.
# So, there is no need to check the response URL: it will always
# match the request. Instead, we will ensure that the response
# code indicates that the request was successful (i.e. no 404, or
# forward to some odd redirect).
if 200 <= r.status_code < 300:
query_status = QueryStatus.CLAIMED
else:
query_status = QueryStatus.AVAILABLE
if dump_response:
print("+++++++++++++++++++++")
print(f"TARGET NAME : {social_network}")
print(f"USERNAME : {username}")
print(f"TARGET URL : {url}")
print(f"TEST METHOD : {error_type}")
try:
print(f"STATUS CODES : {net_info['errorCode']}")
except KeyError:
pass
print("Results...")
try:
print(f"RESPONSE CODE : {r.status_code}")
except Exception:
pass
try:
print(f"ERROR TEXT : {net_info['errorMsg']}")
except KeyError:
pass
print(">>>>> BEGIN RESPONSE TEXT")
try:
print(r.text)
except Exception:
pass
print("<<<<< END RESPONSE TEXT")
print("VERDICT : " + str(query_status))
print("+++++++++++++++++++++")
# Notify caller about results of query.
result: QueryResult = QueryResult(
username=username,
site_name=social_network,
site_url_user=url,
status=query_status,
query_time=response_time,
context=error_context,
)
query_notify.update(result)
# Save status of request
results_site["status"] = result
# Save results from request
results_site["http_status"] = http_status
results_site["response_text"] = response_text
# Add this site's results into final dictionary with all of the other results.
results_total[social_network] = results_site
return results_total
def timeout_check(value):
"""Check Timeout Argument.
Checks timeout for validity.
Keyword Arguments:
value -- Time in seconds to wait before timing out request.
Return Value:
Floating point number representing the time (in seconds) that should be
used for the timeout.
NOTE: Will raise an exception if the timeout in invalid.
"""
float_value = float(value)
if float_value <= 0:
raise ArgumentTypeError(
f"Invalid timeout value: {value}. Timeout must be a positive number."
)
return float_value
def handler(signal_received, frame):
"""Exit gracefully without throwing errors
Source: https://www.devdungeon.com/content/python-catch-sigint-ctrl-c
"""
sys.exit(0)
def main():
parser = ArgumentParser(
formatter_class=RawDescriptionHelpFormatter,
description=f"{__longname__} (Version {__version__})",
)
parser.add_argument(
"--version",
action="version",
version=f"{__shortname__} v{__version__}",
help="Display version information and dependencies.",
)
parser.add_argument(
"--verbose",
"-v",
"-d",
"--debug",
action="store_true",
dest="verbose",
default=False,
help="Display extra debugging information and metrics.",
)
parser.add_argument(
"--folderoutput",
"-fo",
dest="folderoutput",
help="If using multiple usernames, the output of the results will be saved to this folder.",
)
parser.add_argument(
"--output",
"-o",
dest="output",
help="If using single username, the output of the result will be saved to this file.",
)
parser.add_argument(
"--csv",
action="store_true",
dest="csv",
default=False,
help="Create Comma-Separated Values (CSV) File.",
)
parser.add_argument(
"--xlsx",
action="store_true",
dest="xlsx",
default=False,
help="Create the standard file for the modern Microsoft Excel spreadsheet (xlsx).",
)
parser.add_argument(
"--site",
action="append",
metavar="SITE_NAME",
dest="site_list",
default=[],
help="Limit analysis to just the listed sites. Add multiple options to specify more than one site.",
)
parser.add_argument(
"--proxy",
"-p",
metavar="PROXY_URL",
action="store",
dest="proxy",
default=None,
help="Make requests over a proxy. e.g. socks5://127.0.0.1:1080",
)
parser.add_argument(
"--dump-response",
action="store_true",
dest="dump_response",
default=False,
help="Dump the HTTP response to stdout for targeted debugging.",
)
parser.add_argument(
"--json",
"-j",
metavar="JSON_FILE",
dest="json_file",
default=None,
help="Load data from a JSON file or an online, valid, JSON file. Upstream PR numbers also accepted.",
)
parser.add_argument(
"--timeout",
action="store",
metavar="TIMEOUT",
dest="timeout",
type=timeout_check,
default=60,
help="Time (in seconds) to wait for response to requests (Default: 60)",
)
parser.add_argument(
"--print-all",
action="store_true",
dest="print_all",
default=False,
help="Output sites where the username was not found.",
)
parser.add_argument(
"--print-found",
action="store_true",
dest="print_found",
default=True,
help="Output sites where the username was found (also if exported as file).",
)
parser.add_argument(
"--no-color",
action="store_true",
dest="no_color",
default=False,
help="Don't color terminal output",
)
parser.add_argument(
"username",
nargs="+",
metavar="USERNAMES",
action="store",
help="One or more usernames to check with social networks. Check similar usernames using {?} (replace to '_', '-', '.').",
)
parser.add_argument(
"--browse",
"-b",
action="store_true",
dest="browse",
default=False,
help="Browse to all results on default browser.",
)
parser.add_argument(
"--local",
"-l",
action="store_true",
default=False,
help="Force the use of the local data.json file.",
)
parser.add_argument(
"--nsfw",
action="store_true",
default=False,
help="Include checking of NSFW sites from default list.",
)
# TODO deprecated in favor of --txt, retained for workflow compatibility, to be removed
# in future release
parser.add_argument(
"--no-txt",
action="store_true",
dest="no_txt",
default=False,
help="Disable creation of a txt file - WILL BE DEPRECATED",
)
parser.add_argument(
"--txt",
action="store_true",
dest="output_txt",
default=False,
help="Enable creation of a txt file",
)
parser.add_argument(
"--ignore-exclusions",
action="store_true",
dest="ignore_exclusions",
default=False,
help="Ignore upstream exclusions (may return more false positives)",
)
args = parser.parse_args()
# If the user presses CTRL-C, exit gracefully without throwing errors
signal.signal(signal.SIGINT, handler)
# Check for newer version of Sherlock. If it exists, let the user know about it
try:
latest_release_raw = requests.get(forge_api_latest_release, timeout=10).text
latest_release_json = json_loads(latest_release_raw)
latest_remote_tag = latest_release_json["tag_name"]
# if latest_remote_tag[1:] != __version__:
# print(
# f"Update available! {__version__} --> {latest_remote_tag[1:]}"
# f"\n{latest_release_json['html_url']}"
# )
pass # Update check disabled
except Exception as error:
print(f"A problem occurred while checking for an update: {error}")
# Make prompts
if args.proxy is not None:
print("Using the proxy: " + args.proxy)
if args.no_color:
# Disable color output.
init(strip=True, convert=False)
else:
# Enable color output.
init(autoreset=True)
# Check if both output methods are entered as input.
if args.output is not None and args.folderoutput is not None:
print("You can only use one of the output methods.")
sys.exit(1)
# Check validity for single username output.
if args.output is not None and len(args.username) != 1:
print("You can only use --output with a single username")
sys.exit(1)
# Create object with all information about sites we are aware of.
try:
if args.local:
sites = SitesInformation(
os.path.join(os.path.dirname(__file__), "resources/data.json"),
honor_exclusions=False,
)
else:
json_file_location = args.json_file
if args.json_file:
# If --json parameter is a number, interpret it as a pull request number
if args.json_file.isnumeric():
pull_number = args.json_file
pull_url = f"https://api.github.com/repos/sherlock-project/sherlock/pulls/{pull_number}"
pull_request_raw = requests.get(pull_url, timeout=10).text
pull_request_json = json_loads(pull_request_raw)
# Check if it's a valid pull request
if "message" in pull_request_json:
print(f"ERROR: Pull request #{pull_number} not found.")
sys.exit(1)
head_commit_sha = pull_request_json["head"]["sha"]
json_file_location = f"https://raw.githubusercontent.com/sherlock-project/sherlock/{head_commit_sha}/sherlock_project/resources/data.json"
sites = SitesInformation(
data_file_path=json_file_location,
honor_exclusions=not args.ignore_exclusions,
do_not_exclude=args.site_list,
)
except Exception as error:
print(f"ERROR: {error}")
sys.exit(1)
if not args.nsfw:
sites.remove_nsfw_sites(do_not_remove=args.site_list)
# Create original dictionary from SitesInformation() object.
# Eventually, the rest of the code will be updated to use the new object
# directly, but this will glue the two pieces together.
site_data_all = {site.name: site.information for site in sites}
if args.site_list == []:
# Not desired to look at a sub-set of sites
site_data = site_data_all
else:
# User desires to selectively run queries on a sub-set of the site list.
# Make sure that the sites are supported & build up pruned site database.
site_data = {}
site_missing = []
for site in args.site_list:
counter = 0
for existing_site in site_data_all:
if site.lower() == existing_site.lower():
site_data[existing_site] = site_data_all[existing_site]
counter += 1
if counter == 0:
# Build up list of sites not supported for future error message.
site_missing.append(f"'{site}'")
if site_missing:
print(f"Error: Desired sites not found: {', '.join(site_missing)}.")
if not site_data:
sys.exit(1)
# Create notify object for query results.
query_notify = QueryNotifyPrint(
result=None, verbose=args.verbose, print_all=args.print_all, browse=args.browse
)
# Run report on all specified users.
all_usernames = []
for username in args.username:
if check_for_parameter(username):
for name in multiple_usernames(username):
all_usernames.append(name)
else:
all_usernames.append(username)
for username in all_usernames:
results = scan(
username,
site_data,
query_notify,
dump_response=args.dump_response,
proxy=args.proxy,
timeout=args.timeout,
)
if args.output:
result_file = args.output
elif args.folderoutput:
# The usernames results should be stored in a targeted folder.
# If the folder doesn't exist, create it first
os.makedirs(args.folderoutput, exist_ok=True)
result_file = os.path.join(args.folderoutput, f"{username}.txt")
else:
result_file = f"{username}.txt"
if args.output_txt:
with open(result_file, "w", encoding="utf-8") as file:
exists_counter = 0
for website_name in results:
dictionary = results[website_name]
if dictionary.get("status").status == QueryStatus.CLAIMED:
exists_counter += 1
file.write(dictionary["url_user"] + "\n")
file.write(f"Total Websites Username Detected On : {exists_counter}\n")
if args.csv:
result_file = f"{username}.csv"
if args.folderoutput:
# The usernames results should be stored in a targeted folder.
# If the folder doesn't exist, create it first
os.makedirs(args.folderoutput, exist_ok=True)
result_file = os.path.join(args.folderoutput, result_file)
with open(result_file, "w", newline="", encoding="utf-8") as csv_report:
writer = csv.writer(csv_report)
writer.writerow(
[
"username",
"name",
"url_main",
"url_user",
"exists",
"http_status",
"response_time_s",
]
)
for site in results:
if (
args.print_found
and not args.print_all
and results[site]["status"].status != QueryStatus.CLAIMED
):
continue
response_time_s = results[site]["status"].query_time
if response_time_s is None:
response_time_s = ""
writer.writerow(
[
username,
site,
results[site]["url_main"],
results[site]["url_user"],
str(results[site]["status"].status),
results[site]["http_status"],
response_time_s,
]
)
if args.xlsx:
usernames = []
names = []
url_main = []
url_user = []
exists = []
http_status = []
response_time_s = []
for site in results:
if (
args.print_found
and not args.print_all
and results[site]["status"].status != QueryStatus.CLAIMED
):
continue
if response_time_s is None:
response_time_s.append("")
else:
response_time_s.append(results[site]["status"].query_time)
usernames.append(username)
names.append(site)
url_main.append(results[site]["url_main"])
url_user.append(results[site]["url_user"])
exists.append(str(results[site]["status"].status))
http_status.append(results[site]["http_status"])
DataFrame = pd.DataFrame(
{
"username": usernames,
"name": names,
"url_main": [f'=HYPERLINK(\"{u}\")' for u in url_main],
"url_user": [f'=HYPERLINK(\"{u}\")' for u in url_user],
"exists": exists,
"http_status": http_status,
"response_time_s": response_time_s,
}
)
DataFrame.to_excel(f"{username}.xlsx", sheet_name="sheet1", index=False)
print()
query_notify.finish()
if __name__ == "__main__":
main()
================================================
FILE: the_big_brother/sites.py
================================================
"""The Big Brother Sites Information Module
This module supports storing information about websites.
This is the raw data that will be used to search for usernames.
"""
import json
import requests
import secrets
MANIFEST_URL = "https://raw.githubusercontent.com/sherlock-project/sherlock/master/sherlock_project/resources/data.json"
EXCLUSIONS_URL = "https://raw.githubusercontent.com/sherlock-project/sherlock/refs/heads/exclusions/false_positive_exclusions.txt"
class SiteInformation:
def __init__(self, name, url_home, url_username_format, username_claimed,
information, is_nsfw, username_unclaimed=secrets.token_urlsafe(10)):
"""Create Site Information Object.
Contains information about a specific website.
Keyword Arguments:
self -- This object.
name -- String which identifies site.
url_home -- String containing URL for home of site.
url_username_format -- String containing URL for Username format
on site.
NOTE: The string should contain the
token "{}" where the username should
be substituted. For example, a string
of "https://somesite.com/users/{}"
indicates that the individual
usernames would show up under the
"https://somesite.com/users/" area of
the website.
username_claimed -- String containing username which is known
to be claimed on website.
username_unclaimed -- String containing username which is known
to be unclaimed on website.
information -- Dictionary containing all known information
about website.
NOTE: Custom information about how to
actually detect the existence of the
username will be included in this
dictionary. This information will
be needed by the detection method,
but it is only recorded in this
object for future use.
is_nsfw -- Boolean indicating if site is Not Safe For Work.
Return Value:
Nothing.
"""
self.name = name
self.url_home = url_home
self.url_username_format = url_username_format
self.username_claimed = username_claimed
self.username_unclaimed = secrets.token_urlsafe(32)
self.information = information
self.is_nsfw = is_nsfw
return
def __str__(self):
"""Convert Object To String.
Keyword Arguments:
self -- This object.
Return Value:
Nicely formatted string to get information about this object.
"""
return f"{self.name} ({self.url_home})"
from typing import Optional
class SitesInformation:
def __init__(
self,
data_file_path: Optional[str] = None,
honor_exclusions: bool = True,
do_not_exclude: list[str] = [],
):
"""Create Sites Information Object.
Contains information about all supported websites.
Keyword Arguments:
self -- This object.
data_file_path -- String which indicates path to data file.
The file name must end in ".json".
There are 3 possible formats:
* Absolute File Format
For example, "c:/stuff/data.json".
* Relative File Format
The current working directory is used
as the context.
For example, "data.json".
* URL Format
For example,
"https://example.com/data.json", or
"http://example.com/data.json".
An exception will be thrown if the path
to the data file is not in the expected
format, or if there was any problem loading
the file.
If this option is not specified, then a
default site list will be used.
Return Value:
Nothing.
"""
if not data_file_path:
# The default data file is the live data.json which is in the GitHub repo. The reason why we are using
# this instead of the local one is so that the user has the most up-to-date data. This prevents
# users from creating issue about false positives which has already been fixed or having outdated data
data_file_path = MANIFEST_URL
# Ensure that specified data file has correct extension.
if not data_file_path.lower().endswith(".json"):
raise FileNotFoundError(f"Incorrect JSON file extension for data file '{data_file_path}'.")
# if "http://" == data_file_path[:7].lower() or "https://" == data_file_path[:8].lower():
if data_file_path.lower().startswith("http"):
# Reference is to a URL.
try:
response = requests.get(url=data_file_path, timeout=30)
except Exception as error:
raise FileNotFoundError(
f"Problem while attempting to access data file URL '{data_file_path}': {error}"
)
if response.status_code != 200:
raise FileNotFoundError(f"Bad response while accessing "
f"data file URL '{data_file_path}'."
)
try:
site_data = response.json()
except Exception as error:
raise ValueError(
f"Problem parsing json contents at '{data_file_path}': {error}."
)
else:
# Reference is to a file.
try:
with open(data_file_path, "r", encoding="utf-8") as file:
try:
site_data = json.load(file)
except Exception as error:
raise ValueError(
f"Problem parsing json contents at '{data_file_path}': {error}."
)
except FileNotFoundError:
raise FileNotFoundError(f"Problem while attempting to access "
f"data file '{data_file_path}'."
)
site_data.pop('$schema', None)
if honor_exclusions:
try:
response = requests.get(url=EXCLUSIONS_URL, timeout=10)
if response.status_code == 200:
exclusions = response.text.splitlines()
exclusions = [exclusion.strip() for exclusion in exclusions]
for site in do_not_exclude:
if site in exclusions:
exclusions.remove(site)
for exclusion in exclusions:
try:
site_data.pop(exclusion, None)
except KeyError:
pass
except Exception:
# If there was any problem loading the exclusions, just continue without them
print("Warning: Could not load exclusions, continuing without them.")
honor_exclusions = False
self.sites = {}
# Add all site information from the json file to internal site list.
for site_name in site_data:
try:
self.sites[site_name] = \
SiteInformation(site_name,
site_data[site_name]["urlMain"],
site_data[site_name]["url"],
site_data[site_name]["username_claimed"],
site_data[site_name],
site_data[site_name].get("isNSFW",False)
)
except KeyError as error:
raise ValueError(
f"Problem parsing json contents at '{data_file_path}': Missing attribute {error}."
)
except TypeError:
print(f"Encountered TypeError parsing json contents for target '{site_name}' at {data_file_path}\nSkipping target.\n")
return
def remove_nsfw_sites(self, do_not_remove: list = []):
"""
Remove NSFW sites from the sites, if isNSFW flag is true for site
Keyword Arguments:
self -- This object.
Return Value:
None
"""
sites = {}
do_not_remove = [site.casefold() for site in do_not_remove]
for site in self.sites:
if self.sites[site].is_nsfw and site.casefold() not in do_not_remove:
continue
sites[site] = self.sites[site]
self.sites = sites
def site_name_list(self):
"""Get Site Name List.
Keyword Arguments:
self -- This object.
Return Value:
List of strings containing names of sites.
"""
return sorted([site.name for site in self], key=str.lower)
def __iter__(self):
"""Iterator For Object.
Keyword Arguments:
self -- This object.
Return Value:
Iterator for sites object.
"""
for site_name in self.sites:
yield self.sites[site_name]
def __len__(self):
"""Length For Object.
Keyword Arguments:
self -- This object.
Return Value:
Length of sites object.
"""
return len(self.sites)
================================================
FILE: the_big_brother/validators/headless_validator.py
================================================
from dataclasses import dataclass
from typing import Optional
try:
from playwright.sync_api import sync_playwright
except ImportError:
sync_playwright = None
@dataclass
class LinkValidationResult:
url: str
is_profile: bool
reason: Optional[str] = None
title: Optional[str] = None
final_url: Optional[str] = None
visible_text: Optional[str] = None
class HeadlessValidator:
def __init__(self, headless: bool = True):
self.headless = headless
self.playwright = None
self.browser = None
if sync_playwright is None:
print("Warning: Playwright is not installed. Headless validation will fail.")
def __enter__(self):
if sync_playwright:
self.playwright = sync_playwright().start()
self.browser = self.playwright.chromium.launch(headless=self.headless)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self.browser:
self.browser.close()
if self.playwright:
self.playwright.stop()
def validate(self, url: str) -> LinkValidationResult:
if not self.browser:
if not sync_playwright:
return LinkValidationResult(url, False, reason="Playwright not installed")
# If used without context manager, launch for single use (slower)
with sync_playwright() as p:
browser = p.chromium.launch(headless=self.headless)
return self._validate_with_browser(browser, url)
return self._validate_with_browser(self.browser, url)
def _validate_with_browser(self, browser, url: str) -> LinkValidationResult:
context = browser.new_context(
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"
)
page = context.new_page()
try:
# Navigate
response = page.goto(url, timeout=30000, wait_until="domcontentloaded")
if not response:
page.close()
return LinkValidationResult(url, False, reason="No response")
final_url = page.url
title = page.title()
# Status check
if response.status >= 400:
page.close()
return LinkValidationResult(url, False, reason=f"HTTP {response.status}", title=title, final_url=final_url)
# Heuristic 1: Title content
error_keywords_title = ["page not found", "404", "not found", "doesn't exist", "does not exist", "user not found"]
if any(k in title.lower() for k in error_keywords_title):
page.close()
return LinkValidationResult(url, False, reason="Title indicated 404", title=title, final_url=final_url)
# Get text
body_text = page.evaluate("document.body.innerText")
validation_text = body_text[:1000] # First 1000 chars
# Heuristic 2: Body content
error_keywords_body = ["this page isn't available", "sorry, this content isn't available right now"]
if any(k in body_text.lower() for k in error_keywords_body):
page.close()
return LinkValidationResult(url, False, reason="Body content indicated 404", title=title, final_url=final_url)
page.close()
return LinkValidationResult(url, True, title=title, final_url=final_url, visible_text=validation_text)
except Exception as e:
page.close()
return LinkValidationResult(url, False, reason=f"Browsing error: {str(e)}")